A class is the blueprint for objects of a specific type. It lists the instance variables (internal organs) and properties (externally visible organs) of the objects, the messages to which one of these objects can respond (the methods that belong to the object), the messages to which the class as a whole can respond (the methods that belong to the class), and the protocols (obligations) assumed by the objects.
The name of a class always begins with an uppercase letter.
The classes the we create will begin with one uppercase letter.
The classes that have already been created by Apple
begin with two uppercase letters:
NS
,
UI
,
etc.
Let’s
create a class named
Date
.
The file
main.m
demonstrates what we hope to be able to do
with this class
and with objects of this class that we
instantiate
(create).
The description of a class is written in a pair of files,
in this case named
Date.h
and
Date.m
.
The
Date.h
file comes first;
the h stands for “header”.
The
Date.m
file comes second;
the m stands for “implementation”.
This class
Date
serves only to demonstrate how to write a class.
If we really wanted a useful object that would hold a date,
we would instantiate an object of the class
NSDate
that has already been written for us.
It’s a much better class.
Date
.Date
.NSLog
,
etc.)
into the
main
function.
Product Name: Date
Organization Name: John Doe (will appear in copyright notcies)
Company Identifier: edu.nyu.scps (a backwards hostname, e.g., com.qconsf)
Bundle Identifier: edu.nyu.scps.Date
Class prefix: Date (same as the product name)
Devices: iPhone (for the time being)
Do not check Use Core Data.
Press Next.
Date
.
In the Xcode Project Navigator, highlight the folder
Date
.
Cocoa Touch
.
Objective-C class
.
Date
(with an uppercase D
)
NSObject
(with an uppercase NSO
)
Date
folder in the Xcode Project Navigator should contain a new pair of files,
Date.h
and
Date.m
.
Date.h
and
Date.m
files.
main.m
file,
#import
your header file
Date.h
.
In the body of the
main
function,
insert the statements shown
here
to instantiate several
Date
objects and call their methods.
The two new files should initially contain the following.
// // Date.h // Date // // Created by <Username> on 10/20/13. // Copyright (c) 2013 <Organization Name>. All rights reserved. // #import <Foundation/Foundation.h> @interface Date: NSObject @end
// // Date.m // Date // // Created by <Username> on 10/20/13. // Copyright (c) 2013 <Organization Name>. All rights reserved. // #import "Date.h" @implementation Date @end
Class
Date
is a subclass of class
NSObject
,
i.e.,
is derived from class
NSObject
.
This means that
an object of class
Date
(known henceforth as “a
Date
object”)
has everything that an object of class
NSObject
(known henceforth as “an
NSObject
object”)
has, plus more.
For example, a
Date
object contains all the
instance variables
(internal organs)
that an
NSObject
contains.
In addition,
a
Date
object also contains the three instance variables in the
{
curly braces}
Date.h
.
A
Date
object can receive the 13 messages implemented by the 13 methods
marked by a leading minus sign in
Date.m
.NSObject
object
(such as
retainCount
).
init
and
description
are expanded versions of methods of the same name inherited from class
NSObject
,
and therefore do not need to be declared in the
class
interface.
in
Date.h
.
The other 11 methods must be declared in
Date.h
.
Class
Date
can receive the message
yearLength
.
This message is not sent to any object.
It is sent to the class itself,
and is therefore marked with a plus sign.
A method called in response to a message sent to a class is referred to as a
class method.
The declaration (in
Date.h
)
and definition (in
Date.m
)
of a class method are marked with a
plus sign.
A method called in response to a message sent to an object is referred to as an
instance method.
The declaration and definition of an instance method are marked with a
minus sign.
The
year
,
month
,
and
day
methods of class
Date
are called
getters
because they get us the value of an instance variable.
They take no arguments,
and return int
since the instance variables are
int
s.
The data type of the return value is written
in parentheses in front of the name of the method.
The
setYear:
,setMonth:
,setDay:
,Date
are called
setters
because they let us change the value of an instance variable.
They each take one argument of type
int
.
The data type of an argument is written in parentheses after the colon,
followed by the name of the argument.
The file
date.m
Date
,
including the definition (body) of each method of class
Date
.
An implemention file always begins by
#import
ing
the corresponding header file.
A method of one object can easily call another method of the same object.
(An object refers to itself as
self
.)
For example,
next:
calls
next
,
and
next
calls
monthLength
.
Look for the keyword
self
immediately after a left square bracket.
As described in our incantation,
the
init
and
initWithMonth:day:year:
methods of the subclass
(class Date
)
begin by calling the
init
method of the superclass
(class
NSObject
).
If the call to the
init
method of the superclass was successful
(i.e., if it did not return
nil
),
the
init
method of the subclass
then puts values into the instance variables
(year
,
month
,
day
)
of the
Date
object that is being born.
If the entire instantiation was successful,
init
returns the address of the newborn object of class
Date
.
Otherwise, it returns
nil
.
The
init
method of class
Date
performs arcane negotiations with the operating system
to discover today’s date.
The value of
unitFlags
in this method is 28.
But don’t think of it as 28 in decimal.
Think of it as
11100
in binary.
Better yet,
think of it as “yes, yes, yes, no no”.
The three yesses
(When Harry Met Sally
was filmed at
Katz’s
Delicatessen
here on Houston Street)
mean that we want to break today’s date down into
year, month, day.
The multitude of noes mean that we have no interest
in breaking it down into hours, minutes, seconds, etc.
We combine the three yesses with the “bitwise or” operator
|
(a vertical bar).
For these three values,
“bitwise or”
just happens to do the same thing as plain old addition.
NSYearCalendarUnit 00000000000000000000000000000100 (decimal 4) NSMonthCalendarUnit 00000000000000000000000000001000 (decimal 8) NSDayCalendarUnit 00000000000000000000000000010000 (decimal 16) --------------------------------------------------------------------- unitFlags 00000000000000000000000000011100 (decimal 28)
2014-06-08 15:40:17.976 Date[1364:60b] A year has 12 months. 2014-06-08 15:40:17.986 Date[1364:60b] Today is 6/8/2014. 2014-06-08 15:40:17.988 Date[1364:60b] Today is 6/8/2014. 2014-06-08 15:40:17.989 Date[1364:60b] Today is day number 8 out of 30 in month number 6. 2014-06-08 15:40:17.990 Date[1364:60b] Today is day number 8 of the month. 2014-06-08 15:40:17.991 Date[1364:60b] Today is day number 8 of the month. 2014-06-08 15:40:17.993 Date[1364:60b] The second day of this month is 6/2/2014. 2014-06-08 15:40:17.993 Date[1364:60b] Independence Day was 7/4/1776. 2014-06-08 15:40:17.997 Date[1364:60b] America was one month old on 8/4/1776. 2014-06-08 15:40:18.068 Date[1364:60b] Application windows are expected to have a root view controller at the end of application launch
When we apply a dot to a structure, we get or set the value of one of the fields of the structure.
CGPoint p = CGPointMake(0, 0); CGFloat x = p.x; //Get the field. p.x = 10; //Set the field.
When we apply a dot to an object (more precisely, when we apply a dot to a pointer to an object), it looks like we get or set the value of one of the instance variables of the object.
Date *today = [[Date alloc] init]; int y = today.year; //Looks like we're getting the instance variable. today.year = 2000; //Looks like we're setting the instance variable.
But the last two statements actually do the following.
int y = [today year]; //Call the getter method. [today setYear: 2000]; //Call the setter method.The dot operator, together with a pair of appropriately named methods, create the illusion that we can get and set the instance variables of an object just like we get and set the fields of a structure. In reality, the dot operator of an object is a shorthand for calling a getter or setter. A dot that attempts to read an instance variable named
varname
automatically calls the method named
varname
.
A dot that attempts to write an instance variable named
varname
automatically calls the method named
setVarname:
.
The methods called by the above code are declared in the
Date.h
file
- (int) year; //getter - (void) setYear: (int) y; //setter
and defined in the
Date.m
file:
- (int) year { //getter return year; } - (void) setYear: (int) y { //setter year = y; }
Our getters and setters are simple one-liners because our instance variables
are
int
s.
More complicated instance variables would require more complicated
getters and setters.
Instead of declaring and defining the getter and setter ourselves,
we can make the computer behave as if we had declared and defined them.
Remove the above declarations of the methods
year
and
setYear:
from
Date.h
.
Remove the above definitions of the methods
year
and
setYear:
from
Date.m
.
Insert the following statement into the
class interface in
Date.h
between the
}
and the
@end
.
@property (nonatomic, assign) int year;
Insert the following statement into the
class implementation in
Date.m
immediately after the line
@implementation
Date
.year
from an instance variable named
year
.
@synthesize year;
An instance variable that we can access with the dot notation
thanks to a getter and setter that were written for us by
@property
and
@synthesize
is called a
property.
If we don’t write the
@synthesize
line in the current Xcode,
it will behave as if we had written the following (in some versions of Xcode).
It tries to create a property named
year
from an instance variable named
_year
.
@synthesize year = _year;
initWithMonth:day:year:
method of class
Date
placidly accept an invalid month number such as 13 or –1?
If so,
correct it by changing
month = m; //assigns directly to the instance variableto
self.month = m; //means [self setMonth: m];Verify that
initWithMonth:day:year:
now prints an error message with
NSLog
when it receives an invalid month.
Error check the day too, but not the year.
int
value,
next
method of class
Date
print an error message with
NSLog
instead of blindly executing the
++year
#import
<limits.h>
INT_MAX
(all uppercase, one underscore)
instead of
2147483647.
init
and
initWithMonth:day:year:
methods of class
Date
,
we can abbreviate
self = [super init]; if (self != nil) {to any one of the four following fragments. Which is clearest?
explicit comparison to nil | implicit comparison to nil | |
---|---|---|
assignment in separate statement |
self = [super init]; if (self != nil) { |
self = [super init]; if (self) { |
assignment in same statement |
if ((self = [super init]) != nil) { |
if (self = [super init]) { |
if
statement will be true if the call to the
init
method of the
super
object was successful.
In that case,
we can go ahead and store values into the instance variables
year
,
month
,
and
day
that were introduced in the subclass.
An
init
method always returns the pointer
self
,
which points to the object that was just instantiated.
prev
and
prev:
for class
Date
,
analogous to
next
and
next:
.
Call them in the
main
function to demonstrate that they work.
NSString *reminder; //inside the {curly braces} in Date.h
//Initilize the reminder to point to the empty string //in the methods init and initWithMonth:day:year: in Date.m. reminder = @"";
//The string returned by the description method in Date.m //should display the reminder. return [NSString stringWithFormat: @"%d/%d/%d %@", month, day, year, reminder];An instance variable that is a pointer to an object requires the word
strong
instead of
assign
when you turn it into a property.
//in Date.h @property (nonatomic, strong) NSString *reminder;
//in Date.m @synthesize reminder;
In the
main
function,
call the setter.
//After you create independenceDay, but before you print it with NSLog. independenceDay.reminder = @"Fireworks tonight";
yearLength
method
in
Date.m
+ (int) yearLength { NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar ]; //What is the range of months in the year that contains today? NSRange range = [calendar rangeOfUnit: NSMonthCalendarUnit inUnit: NSYearCalendarUnit forDate: [[NSDate alloc] init] ]; return (int)range.length; //How many months are in the range of months? }
NSGregorianCalendar
to
NSHebrewCalendar
,
NSIslamicCalendar
,
etc.
For calendars in which a year does not always have the same number of months,
you will have to change
yearLength
from a class method (marked with a plus sign)
to an instance method (marked with a minus sign).