Once upon a time a man named Brad Cox decided that it was time for the world to move toward a more modular programming style. C was a popular and powerful language. Smalltalk was an elegant untyped object-oriented language. Starting with C, Brad Cox added Smalltalk-like classes and message-sending mechanisms. He called the result Objective-C. Objective-C is a very simple extension of the C language. In fact, it was originally just a C preprocessor and a library.
Objective-C is not a proprietary language. Rather, it is an open standard that has been included in the Free Software Foundation's GNU C compiler (gcc) for many years. Cocoa was developed using Objective-C, and most Cocoa programming is done in Objective-C.
Teaching C and basic object-oriented concepts could consume an entire book. Instead of writing that book, this chapter assumes that you already know a little C and something about objects and introduces you to the basics of Objective-C. If you fit the profile, you will find learning Objective-C to be easy. If you do not, Apple's The Objective-C Language is a more gentle introduction.
Chapter 1 mentioned that classes are used to create objects, that the objects have methods, and that you can send messages to the objects to trigger these methods. In this section, you will learn how to create an object, send messages to it, and destroy it when you no longer need it.
As an example, we will use the class NSMutableArray
. You can create a new instance of NSMutableArray
by sending the message alloc
to the NSMutableArray
class like this:
[NSMutableArray alloc];
This method returns a pointer to the space that was allocated for the object. You could hold onto that pointer in a variable like this:
NSMutableArray *foo; foo = [NSMutableArray alloc];
While working with Objective-C, it is important to remember that foo
is just a pointer. In this case, it points to an object.
Before using the object that foo
points to, you would need to make sure that it is fully initialized. The init
method will handle this task, so you might write code like this:
NSMutableArray *foo; foo = [NSMutableArray alloc]; [foo init];
Take a long look at the last line; it sends the message init
to the object that foo
points to. We would say, “foo
is the receiver of the message init
.” Notice that a message send consists of a receiver (the object foo
points to) and a message (init
) wrapped in square brackets. Note that you can also send messages to classes, as demonstrated by sending the message alloc
to the class NSMutableArray
.
The method init
actually returns the newly initialized object. As a consequence, you will always nest the message sends like this:
NSMutableArray *foo; foo = [[NSMutableArray alloc] init];
What about destroying the object when we no longer need it?
[foo release];
We will discuss release
and what it really means later in this chapter.
Some methods take arguments. If a method takes an argument, the method name (called a selector) will end with a colon. For example, to add objects to the end of the array, you use the addObject:
method (assume bar
is a pointer to another object):
If you have multiple arguments, the selector will have multiple parts. For example, to add an object at a particular index, you could use the following:
[foo insertObject:bar atIndex:5];
Note that insertObject:atIndex:
is one selector, not two. It will trigger one method with two arguments. This outcomes seems strange to most C and Java programmers, but should be familiar to Smalltalk programmers. The syntax also makes your code easier to read. For example, it is not uncommon to see a C++ method call like this:
if (x.intersectsArc(35.0, 19.0, 23.0, 90.0, 120.0))
It is much easier to guess the meaning of the following code:
if ([x intersectsArcWithRadius:35.0 centeredAtX:19.0 Y:23.0 fromAngle:90.0 toAngle:120.0])
If it seems odd right now, just use it for a while. Most programmers grow to really appreciate the Objective-C messaging syntax.
You are now at a point where you can read simple Objective-C code, so it is time to write a program that will create an instance of NSMutableArray
and fill it with 10 instances of NSNumber
.
If it isn't running, start Xcode. Close any projects that you were working on. Under the Project menu, choose New Project…. When the panel pops up, choose to create a Foundation Tool (Figure 3.1).
Name the project lottery (Figure 3.2). Unlike the names of applications, most tool names are lowercase.
A Foundation tool has no graphical user interface and typically runs on the command line or in the background as a daemon. Unlike in an application project, you will always alter the main.m
file of a Foundation tool.
When the new project appears, select main.m
under Source. It should look like Figure 3.3.
Edit main.m
to look like this:
#import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSMutableArray *array; int i; NSNumber *newNumber; NSNumber *numberToPrint; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; array = [[NSMutableArray alloc] init]; for ( i = 0; i < 10; i++) { newNumber = [[NSNumber alloc] initWithInt:(i * 3)]; [array addObject:newNumber]; // If you already know some Cocoa, you might notice that // I have a memory leak here. We will fix it soon. } for ( i = 0; i < 10; i++) { numberToPrint = [array objectAtIndex:i]; NSLog(@"The number at index %d is %@", i, numberToPrint); } [array release]; [pool release]; return 0; }
Here is the play-by-play for the code:
#import <Foundation/Foundation.h>
You are including the headers for all the classes in the Foundation framework. The headers are precompiled, so this approach is not as computationally intensive as it sounds.
int main (int argc, const char *argv[])
The main
function is declared just as it would be in any Unix C program.
NSMutableArray *array; int i; NSNumber *newNumber; NSNumber *numberToPrint;
Four variables are declared here: array
is a pointer to an instance of NSMutableArray
, i
is an int, and the pointers newNumber
and numberToPrint
each point to an instance of NSNumber
. Note that no objects actually exist yet. You have simply declared pointers that will refer to the objects once they are created.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
This code declares a variable and points it to a new instance of NSAutoreleasePool
. We will discuss the importance of autorelease pools later in the chapter.
for (i = 0; i < 10; i++) { newNumber = [[NSNumber alloc] initWithInt:(i*3)]; [array addObject:newNumber]; }
Inside the for
loop you have created an instance of NSNumber
and made the variable newNumber
point to it. Then you have added that object to the array. This case offers a perfect example of the difference between the pointer and the object it points to. Here you have one variable of type NSNumber *
, but you have 10 instances of NSNumber
(Figure 3.4).
The array does not make copies of the NSNumber
objects. Instead, it simply keeps a list of pointers to the NSNumber
objects. Objective-C programmers make very few copies of objects, because it is seldom necessary.
for ( i = 0; i < 10; i++) { numberToPrint = [array objectAtIndex:i]; NSLog(@"The number at index %d is %@", i, numberToPrint); }
Here you are printing the contents of the array to the console. NSLog
is a function much like printf()
; it takes a format string and a comma-separated list of variables to be substituted into the format string. When displaying the string, NSLog
prefixes the generated string with the name of the application and a time stamp.
In printf
, for example, you would use %x
to display an integer in hexadecimal form. With NSLog
, we have all the tokens from printf
and the token %@
to display an object. The object gets sent the message description
, and the string it returns replaces %@
in the string. We will discuss the description
method in detail soon.
All the tokens recognized by NSLog()
are listed in Table 3.1.
Table 3.1. Possible Tokens in Objective-C Format Strings
Symbol | Displays |
---|---|
%@ | Id |
%d, %D, %i | long |
%u, %U | unsigned long |
%hi | short |
%hu | unsigned short |
%qi | long long |
%qu | unsigned long long |
%x, %X | unsigned long printed as hexadecimal |
%o, %O | unsigned long printed as octal |
%f, %e, %E, %g, %G | double |
%c | unsigned char as ASCII character |
%C | unichar as Unicode character |
%s | char * (a null-terminated C string of ASCII characters) |
%S | unichar * (a null-terminated C string of Unicode characters) |
%p | void * (an address printed in hexadecimal with a leading 0x) |
%% | A % character |
Note: If the @
symbol before the quotes in @
”The number at index %d is %@” looks a little strange, remember that Objective-C is the C language with a couple of extensions. One of the extensions is that strings are instances of the class NSString
. In C, strings are just pointers to a buffer of characters that ends in the null character. Both C strings and instances of NSString
can be used in the same file. To differentiate between constant C strings and constant NSString
s, you must put @
before the opening quote of a constant NSString
.
// C string char *foo; // NSString NSString *bar; foo = "this is a C string"; bar = @"this is an NSString";
You will use mostly NSString
in Cocoa programming. Wherever a string is needed, the classes in the frameworks expect an NSString
. However, if you already have a bunch of C functions that expect C strings, you will find yourself using char *
frequently.
You can convert between C strings and NSString
s:
const char *foo = "Blah blah" NSString *bar; // Create an NSString from a C string bar = [NSString stringWithUTF8String:foo]; // Create a C string from an NSString foo = [bar UTF8String];
Because NSString
can hold Unicode strings, you will need to deal with the multibyte characters correctly in your C strings, and this can be quite difficult and time-consuming. (Besides the multibyte problem, you will have to wrestle with the fact that some languages read from right to left.) Whenever possible, you should use NSString
instead of C strings.
Continuing with the code in main()
, you have the following:
[array release]; [pool release]; return(0); }
Now that you no longer need the array or the autorelease pool, you send them the release
message so that they will be deallocated.
Build the program. The log (which will contain the output) should appear automatically.
Build and run the program (Figure 3.5).
Let's discuss the release
method. Every object has a retain count. The retain count is an integer. When an object is created by the alloc
method, the retain count is set to 1. When the retain count becomes zero, the object is deallocated. You increment the retain count by sending the message retain
to the object. You decrement the retain count by sending the message release
to the object.
Why is there a retain count at all? In C, after all, you would simply malloc
memory and then free
the memory when it is no longer needed. Imagine for a moment that you are an object that would be useful to several other objects. They are willing to share you, but how will you get deallocated when none of the other objects need you any more? When an object gets a reference to you, it will send you a retain
message. When an object no longer needs you, it will send you a release
message. Thus, your retain count represents how many other objects have references to you. When the retain count becomes zero, this indicates that no one cares about you any more. You are deallocated so that the memory you were occupying can be freed.
A commonly used analogy is that of the dog and the leash. Each person who wants to ensure that the dog will stay around retains the dog by attaching a leash to its collar. Many people can retain the dog, and as long as at least one person is retaining the dog, the dog will not go free. When zero people are retaining the dog, it will be freed. The retain count of an object, then, is the number of “leashes” on that object (Figure 3.6).
Whereas Java has a garbage collector, Objective-C offers its retain count mechanism. It gives the developer lots of control over how and when objects are freed, but it requires that you meticulously retain and release objects. You will spend some time every day walking through code and thinking about the retain count of the objects involved. If you release an object too much, it will be freed prematurely and your program will crash. If you retain an object too much, it will never get freed and you will waste memory.
When you ran your program a moment ago, it worked, didn't it? You would like to think that all 88the NSNumber
objects neatly deallocated themselves when the array was released. If you believed that, you would be fooling yourself.
An array does not make a copy of an object when it is added. Instead, the array stores a pointer to the object and sends it the message retain
. When the array is deallocated, the objects in the array are sent the message release
. (Also, if an object is removed from an array, it is sent release
.)
Back to the lottery project: We have a memory leak. Let's quickly go over the life of the NSNumber
in your application:
When the number object is created, it has a retain count of 1.
When the number object is added to the array, its retain count is incremented to 2.
When the array is deallocated, it releases the number. This decrements the retain count to 1.
The number object is not deallocated. In this example, the process ends an instant later, and the operating system reclaims all the memory. Thus the lack of deallocation is not a big deal. However, in a program that ran a long time, such a memory leak would be a bad thing. To practice being a tidy Objective-C programmer, fix the code.
After inserting the number into the array, release it. The revised loop should look like this:
for (i = 0; i < 10; i++) {
newNumber = [[NSNumber alloc] initWithInt:(i*3)];
[array addObject:newNumber];
[newNumber release];
}
In most object-oriented languages, your program will crash if you send a message to nil
. In applications written in those languages, you will see many checks for nil
before sending a message. In Java, for example, you frequently see the following:
if (foo != null) { foo.doThatThingYouDo(); }
In Objective-C, it is okay to send a message to nil
. The message is simply discarded, which eliminates the need for these sorts of checks. For example, this code will build and run without an error:
id foo; foo = nil; [foo count];
This approach is different from how most languages work, but you will get used to it.
Although you may send a message to nil
, sending a message to a freed object will crash your program. For example, this code would not work:
id foo = [[NSMutableArray alloc] init]; [foo release]; [foo count];
To prevent this possibility, many developers set the pointer to nil
after releasing an object:
[foo release]; foo = nil;
If you find yourself asking over and over “Why isn't this gosh-darned method getting called?”, chances are that the pointer you are using, assuming it is not nil
, is actually nil
.
You now have a fine program that uses a couple of the classes that came with Cocoa: NSObject
, NSMutableArray
, and NSString
. (All classes that come with Cocoa have names with the “NS
” prefix. Classes that you will create will not start with “NS
”.) These classes are all part of the Foundation framework. Figure 3.7 shows an inheritance diagram for these classes.
Let's go through a few of the commonly used methods on these classes. For a complete listing, you can access the online documentation in /Developer/ Documentation/Cocoa/Reference/Foundation/ObjC_classic/
NSObject
is the root of the entire Objective-C class hierarchy. Some commonly used methods on NSObject
are described next.
- (id)init
Initializes the receiver after memory for it has been allocated. An init
message is generally coupled with an alloc
message in the same line of code:
TheClass *newObject = [[TheClass alloc] init];
+(id)new
Calls alloc
to create a new instance and sends init
to that new instance. For example, you can create a new instance of NSMutableArray
like this:
myArray = [[NSMutableArray alloc] init];
or like this:
myArray = [NSMutableArray new];
Both are equivalent.
Returns an NSString
that describes the receiver. The debugger's print object command (“po”) invokes this method. A good description
method will often make debugging easier. Also, if you use %@
in a format string, the object that should be substituted in is sent the message description
. The value returned by the description
method is put into the log string. For example, the line in your main function
NSLog(@"The number at index %d is %@", i, numberToPrint);
is equivalent to
Increments the receiver's retain count.
Decrements the receiver's retain count and sends it a dealloc
message if its retain count becomes zero.
Deallocates the object's memory. It is similar to a destructor in C++. This method is called automatically when the retain count becomes zero; you should not call it directly.
- (BOOL)isEqual:(id)anObject
{ PRIVATE xe "isEqual" xe }Returns YES
if the receiver and anObject
are equal and NO
otherwise. You might use it like this:
if ([myObject isEqual:anotherObject]) { NSLog(@"They are equal."); }
But what does “equal” really mean? In NSObject
, this method is defined to return YES
if and only if the receiver and anObject
are the same object—that is, if both are pointers to the same memory location.
Clearly, this is not always the “equal” that you would hope for, so this method is overridden by many classes to implement a more appropriate idea of equality. For example, NSString
overrides the method to compare the characters in the receiver and anObject
. If the two strings have the same characters in the same order, they are considered equal.
Thus, if x
and y
are NSStrings
, there is a big difference between these two expressions:
x == y
and
[x isEqual:y]
The first expression compares the two pointers. The second expression compares the characters in the strings. Note, however, that if x
and y
are instances of a class that has not overridden NSObject
's isEqual:
method, the two expressions are equivalent.
An NSArray
is a list of pointers to other objects. It is indexed by integers: Thus, if there are n objects in the array, the objects are indexed by the integers 0 through n – 1. You cannot put a nil
in an NSArray
. (This means that there are no “holes” in an NSArray
, which may confuse some programmers who are used to Java's Object[]
.) NSArray
inherits from NSObject
.
An NSArray
is created with all the objects that will ever be in it. You can neither add nor remove objects from an instance of NSArray
. We say that NSArray
is immutable. (Its mutable subclass, NSMutableArray
, will be discussed next.) Immutability is nice in some cases. Because it is immutable, a horde of objects can share one NSArray
without worrying that one object in the horde might change it. NSString
and NSNumber
are also immutable. Instead of changing a string or number, you will simply create another one with the new value. (In the case of NSString
, there is also the class NSMutableString
that allows its instances to be altered.)
Here are some commonly used methods implemented by NSArray
:
- (unsigned)count
Returns the number of objects currently in the array.
- (id)objectAtIndex:(unsigned)i
Returns the object located at index i
. If i
is beyond the end of the array, you will get an error at runtime.
Returns the object in the array with the highest index value. If the array is empty, nil
is returned.
Returns YES
if anObject
is present in the array. This method determines whether an object is present in the array by sending an isEqual:
message to each of the array's objects and passing anObject
as the parameter.
Searches the receiver for anObject
and returns the lowest index whose corresponding array value is equal to anObject
. Objects are considered equal if isEqual:
returns YES
. If none of the objects in the array are equal to anObject
, indexOfObject:
returns NSNotFound
.
NSMutableArray
inherits from NSArray
but extends it with the ability to add and remove objects. Objects are retained when added and are released when removed. To create a mutable array from an immutable one, use NSArray
's mutableCopy
method.
Here are some commonly used methods implemented by NSMutableArray
:
Inserts anObject
at the end of the receiver. You are not allowed to add nil
to the array.
- (void)addObjectsFromArray:(NSArray *)otherArray
Adds the objects contained in otherArray
to the end of the receiver's array of objects.
- (void)insertObject:(id)anObject atIndex:(unsigned)index
{ PRIVATE xe "insertObject:atIndex" xe }Inserts anObject
into the receiver at index
. If index
is already occupied, the objects at index
and beyond are shifted up one slot to make room. index
cannot be greater than the number of elements in the array. You will get an error if anObject
is nil
or if index
is greater than the number of elements in the array.
- (void)removeAllObjects
{ PRIVATE xe "removeAllObjects" xe }Empties the receiver of all its elements.
- (void)removeObject:(id)anObject
Removes all occurrences of anObject
in the array. Matches are determined on the basis of anObject
's response to the isEqual:
message.
- (void)removeObjectAtIndex:(unsigned)index
Removes the object at index
and moves all elements beyond index
down one slot to fill the gap. You will get an error if index
is beyond the end of the array.
As mentioned earlier, you cannot add nil
to an array. Sometimes you will want to put an object into an array to represent nothingness. The NSNull
class exists for exactly this purpose. There is exactly one instance of NSNull
, so if you want to put a placeholder for nothing into an array, use NSNull
like this:
[myArray addObject:[NSNull null]];
An NSString
is a buffer of Unicode characters. In Cocoa, all manipulations involving character strings are done with NSString
. As a convenience, the Objective-C language also supports the @"…"
construct to create a string object constant from a 7-bit ASCII encoding:
NSString *temp = @"this is a constant string";
NSString
inherits from NSObject
. Here are some commonly used methods implemented by NSString
:
- (id)initWithFormat:(NSString *)format, ...
Works like sprintf
. Here format
is a string containing tokens like %d. The additional arguments are substituted for the tokens:
int x = 5;
char *y = "abc";
id z = @"123";
NSString *aString = [[NSString alloc] initWithFormat:
@"Here's the int %d, the C String %s, and the NSString
%@", x, y, z];
- (unsigned int)length
Returns the number of characters in the receiver.
- (NSString *)stringByAppendingString:(NSString *)aString
Returns a string object made by appending aString
to the receiver. The following code snippet, for example, would produce the string “Error: unable to read file.”
NSString *errorTag = @"Error: "; NSString *errorString = @"unable to read file."; NSString *errorMessage; errorMessage = [errorTag stringByAppendingString:errorString];
Beginning Cocoa programmers are often eager to create subclasses of NSString
and NSMutableArray
. Don't. Stylish Objective-C programmers almost never do. Instead, they use NSString
and NSMutableArray
as parts of larger objects, a technique known as composition. For example, a BankAccount
class could be a subclass of NSMutableArray
. After all, isn't a bank account simply a collection of transactions? The beginner would follow this path. In contrast, the old hand would create a class BankAccount
that inherited from NSObject
and has an instance variable called transactions
that would point to an NSMutableArray
.
It is important to keep track of the difference between “uses” and “is a subclass of.” The beginner would say, “BankAccount
inherits from NSMutableArray
.” The old hand would say, “BankAccount
uses NSMutableArray
.” In the common idioms of Objective-C, “uses” is much more common than “is a subclass of.”
You will find it is much easier to use a class than it is to subclass one. Subclassing involves more code and requires a deeper understanding of the superclass. By using composition instead of inheritance, Cocoa developers can take advantage of very powerful classes without really understanding how they work.
In a strongly typed language like C++, inheritance is crucial. In an untyped language like Objective-C, inheritance is just a hack that saves the developer some typing. There are only two inheritance diagrams in this entire book. All the other diagrams are object diagrams that indicate which objects know about which other objects. This is much more important information to a Cocoa programmer.
Where I live, the state government has decided that the uneducated have entirely too much money: You can play the lottery every week here. Let's imagine that a lottery entry has two numbers between 1 and 100, inclusive. You will write a program that will make up lottery entries for the next 10 weeks. Each LotteryEntry
object will have a date and two random integers (Figure 3.9). Besides learning how to create classes, you will build a tool that will certainly make you fabulously wealthy.
First you will create files for the LotteryEntry
class. In the File menu, choose New file…. Select Objective-C class as the type (Figure 3.10).
Name the file LotteryEntry.m
(Figure 3.11). Note that you are also causing LotteryEntry.h
to be created.
Edit the LotteryEntry.h
file to look like this:
#import <Foundation/Foundation.h> @interface LotteryEntry : NSObject { NSCalendarDate *entryDate; int firstNumber; int secondNumber; } - (void)prepareRandomNumbers; - (void)setEntryDate:(NSCalendarDate *)date; - (NSCalendarDate *)entryDate; - (int)firstNumber; - (int)secondNumber; @end
You have created a header file for a new class called LotteryEntry
that inherits from NSObject
. It has three instance variables:
entryDate
is an NSCalendarDate
.
firstNumber
and secondNumber
are both int
s.
You have declared five methods in the new class:
prepareRandomNumbers
will set firstNumber
and secondNumber
to random values between 1 and 100. It takes no arguments and returns nothing.
entryDate
and setEntryDate:
will allow other objects to read and set the variable entryDate
. The method entryDate
will return the value stored in the entryDate
variable. The method setEntryDate:
will allow the value of the entryDate
variable to be set. Methods that allow variables to be read and set are called accessor methods.
You have also declared accessor methods for reading firstNumber
and secondNumber
. (You have not declared accessors for setting these variables; you are going to set them directly in prepareRandomNumbers
.)
Edit LotteryEntry.m
to look like this:
#import "LotteryEntry.h" @implementation LotteryEntry - (void)prepareRandomNumbers { firstNumber = random() % 100 + 1; secondNumber = random() % 100 + 1; } - (void)setEntryDate:(NSCalendarDate *)date { [date retain]; [entryDate release]; [date setCalendarFormat:@"%b %d, %Y"]; entryDate = date; } - (NSCalendarDate *)entryDate { return entryDate; } - (int)firstNumber { return firstNumber; } - (int)secondNumber { return secondNumber; } - (void)dealloc { NSLog(@"Destroying %@", self); [entryDate release]; [super dealloc]; } @end
Here is the play-by-play for each method:
prepareRandomNumbers
uses the standard random
function to generate a pseudo-random number. You use the mod operator (%
) and add 1 to get the number in the range 1–100.
setEntryDate:
releases the old date and retains the new one. You then set the format on the date object so that it will display itself like this: “Feb 14, 1934.” Next, you set the entryDate
variable to point to the new value.
entryDate
, firstNumber
, and secondNumber
return the values of variables.
So far, the most interesting part of this class is the dealloc
method. It will be called automatically when the retain count becomes zero. The first thing in the method is the line
NSLog(@"Destroying %@", self);
Printing this message indicates that the dealloc
method got called.
Notice the use of self
. Every method has an implicit local variable called self
, which is a pointer to the object that is executing the method. You will often use self
to have an object send a message to itself like this:
[self setNeedsDisplay:YES];
Continuing with the dealloc
method, you come to the line
[entryDate release];
Here entryDate
is a pointer to an NSCalendarDate
object. This line informs the object that you are no longer interested in it and decrements the retain count. If the retain count goes to zero, the NSCalendarDate
object will also be deallocated.
Notice that you do not have to release the int
s. Because they are not objects, they actually reside inside the LotteryEntry
object. When it is deallocated, the space will be freed. By contrast, entryDate
is a pointer to an object that is outside the LotteryEntry
object, so you must release it.
The last line,
[super dealloc];
shows how we can call a superclass's implementation of a method. Here we are calling the dealloc
method of the superclass (NSObject
, in this example). NSObject
's dealloc
method actually frees the memory of the object, so make sure that it is always the last line of the dealloc
method.
Now let's look at main.m
. Many of the lines have stayed the same, but several have changed. The most important change is that we are using LotteryEntry
objects instead of NSNumber
objects.
Here is the heavily commented code. (You don't have to type in the comments.)
#import <Foundation/Foundation.h> #import "LotteryEntry.h" int main (int argc, const char *argv[]) { NSMutableArray *array; int i; LotteryEntry *newEntry; LotteryEntry *entryToPrint; NSCalendarDate *now; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Create the date object now = [[NSCalendarDate alloc] init]; // Initialize the random number generator srandom(time(NULL)); array = [[NSMutableArray alloc] init]; for (i = 0; i < 10; i++){ // Create a new instance of LotteryEntry newEntry = [[LotteryEntry alloc] init]; [newEntry prepareRandomNumbers]; // Create a date/time object that is i weeks from now [newEntry setEntryDate:[now dateByAddingYears:0 months:0 days:(i * 7) hours:0 minutes:0 seconds:0]]; // Add the LotteryEntry object to the array [array addObject:newEntry]; // Decrement the retain count of the lottery entry [newEntry release]; } for (i = 0; i < 10; i++){ // Get an instance of LotteryEntry entryToPrint = [array objectAtIndex:i]; // Display its contents NSLog(@"entry %d is %@", i, entryToPrint); } [array release]; // Release the current time [now release]; [pool release]; return 0; }
This program will create an array of LotteryEntry
objects, as shown in Figure 3.11.
Build and run your application. You should see something like Figure 3.12.
Hmm. Not quite what we hoped for. After all, the program is supposed to reveal the dates and the numbers you should play on those dates, and you can't see either. Next you will make the LotteryEntry
objects display themselves in a more meaningful manner.
As we saw in the NSObject
summary, every object has a description
method that returns a string. The default method returns the name of the class and the hexadecimal address of the object. It would look like this: <LotteryEntry: 0x3da23>
. You will now override the description
method so that it returns a more meaningful string.
A first attempt might look something like this:
- (NSString *)description { NSString *result; result = [[NSString alloc] initWithFormat:@"%@ = %d and %d", entryDate, firstNumber, secondNumber]; return result; }
This code would work perfectly well, but would result in an annoying memory leak. The alloc
operation always yields an object with a retain count of 1; thus the string being returned has a retain count of 1. Any object asking for the string would retain it. The string would then have a retain count of 2. When the object was no longer interested in the string, it would release it. The retain count would become 1. As you see, the string would never be deallocated.
Our next attempt might look something like this:
- (NSString *)description { NSString *result; result = [[NSString alloc] initWithFormat:@"%@ = %d and %d", entryDate, firstNumber, secondNumber]; [result release]; return result; }
This code would not work at all. When sent the message release
, the string's retain count would go to zero, and the string would be deallocated. The object asking for the string would get a pointer to a freed object.
The problem, then, is that you need to return a string, but you do not want to retain it. This is a common problem throughout the frameworks, which leads us to NSAutoreleasePool
.
Objects are added to the current autorelease pool when they are sent the message autorelease
. When the autorelease pool is deallocated, it sends the message release
to all objects in the pool.
In other words, when an object is autoreleased, it is marked to be sent release
sometime in the future. In particular, in a Cocoa application, an autorelease pool is created before every event is handled and deallocated after the event has been handled. Thus, unless the objects in the autorelease pools are being retained, they will be destroyed as soon as the event has been handled.
A correct solution then is
- (NSString *)description { NSString *result; result = [[NSString alloc] initWithFormat:@"%@ = %d and %d", entryDate, firstNumber, secondNumber]; [result autorelease]; return result; }
Rules Concerning Release
Objects created by alloc
, new
, copy
, or mutableCopy
have a retain count of 1 and are not in the autorelease pool.
If you get an object by any other method, assume that it has a retain count of 1 and is in the autorelease pool. If you do not wish it to be deallocated with the current autorelease pool, you must retain it.
Because you will frequently need objects that you are not retaining, many classes have class methods that return autoreleased objects. NSString
, for example, has stringWithFormat:
. The simplest correct solution then would be
- (NSString *)description { return [NSString stringWithFormat:@"%@ = %d and %d", entryDate, firstNumber, secondNumber]; }
Note that this is equivalent to the code from the previous version. Add this code to your LotteryEntry.m
file, and then build and run your application (Figure 3.13).
Notice that the autoreleased object won't be released until the event loop ends. This behavior makes it perfect for providing an intermediate result. For example, if you had an array of NSString
objects, you could create a string with all the elements in uppercase and concatenated together, like this:
- (NSString *)concatenatedAndAllCaps { int i; NSString *sum = @""; NSString *upper; for (i=0; i < [myArray count]; i++) { upper = [[myArray objectAtIndex:i] uppercaseString]; sum = [NSString stringWithFormat:@"%@%@", sum, upper]; } return sum; }
With this method, if you have 13 strings in the array, 26 autoreleased strings will be created (13 by uppercaseString
and 13 by stringWithFormat:
; the initial constant string is a special case and doesn't count). One of the resulting strings is returned and may be retained by the object that asked for it. The other 25 strings are deallocated automatically at the end of the event loop. (Note that you would probably get better performance in this example by appending the uppercased string to an NSMutableString
instead of creating a brand-new string and adding it to the autorelease pool each time through the loop.)
An object has instance variables. Other objects cannot access these variables directly. To enable other objects to read and set an instance variable, an object will usually have a pair of accessor methods.
For example, if a class Rex
has an instance variable named fido
, the class will probably have at least two other methods: fido
and setFido:
. The fido
method enables other objects to read the fido
variable; the setFido:
method enables other objects to set the fido
variable.
If you have a nonpointer type, the accessor methods are quite simple. For example, if your class has an instance variable called foo
of type int
, you would create the following accessor methods:
- (int)foo { return foo; } - (void)setFoo:(int)x { foo = x; }
These methods will allow other objects to get and set the value of foo
.
Matters become more complicated if foo
is a pointer to an object. In the “setter” method, you need to make sure that the new value is retained and the old value is released, as shown in Figure 3.14. If you assume that foo
is a pointer to an NSCalendarDate
, there are three common idioms in setter methods. All three work correctly, and you can probably find some experienced Cocoa programmers who will argue the superiority of any one of them. I'll list the tradeoffs after each one.
The first idiom is “Retain, Then Release”:
- (void)setFoo:(NSCalendarDate *)x { [x retain]; [foo release]; foo = x; }
Here it is important to retain before releasing. Suppose that you reverse the order. If x
and foo
are both pointers to the same object, the release would cause the object to be deallocated before it was retained. Tradeoff: If they are the same value, this method performs an unnecessary retain and release.
The second idiom is “Check Before Change”:
- (void)setFoo:(NSCalendarDate *)x { if (foo != x) { [foo release]; foo = [x retain]; } }
Here you are not setting the variable unless a different value is passed in. Tradeoff: An extra if
statement is necessary.
The final idiom is “Autorelease Old Value”:
- (void)setFoo:(NSCalendarDate *)x { [foo autorelease]; foo = [x retain]; }
Here, you autorelease the old value. Tradeoff: An error in retain counts will result in a crash one event loop after the error. This behavior makes the bug harder to track down. In the first two idioms, your crash will happen closer to your error. Also, autorelease
carries some performance overhead.
You have read the tradeoffs and you can make your own decision on which to use. In this book, I will use “Retain, Then Release.”
The “getter” method for an object is the same as that for a nonpointer type:
- (NSCalendarDate *)foo { return foo; }
Most Java programmers would name this method getFoo
. Don't. Objective-C programmers call this method foo
. In the common idioms of Objective-C, a method prefixed with “get” takes an address where data can be copied. For example, if you have an NSColor
object and you want its red, green, blue, and alpha components, you would call getRed:green:blue:alpha:
as follows:
float r, g, b, a; [myFavoriteColor getRed:&r green:&g blue:&b alpha:&a];
(For readers who might be a bit rusty with their C, “&” returns the address where the variable holds its data.)
If you used your accessor methods to read the variables, your description
method would look like this:
- (NSString *)description { return [NSString stringWithFormat:@"%@ = %d and %d", [self entryDate], [self firstNumber], [self secondNumber]]; }
This would be considered the “most correct” implementation of the description
method.
Before moving on to any new ideas, let's examine NSCalendarDate
in some depth. Instances of NSCalendarDate
have a date and time, a time zone, and a format string. NSCalendarDate
inherits from NSDate
.
Instances of NSCalendarDate
are basically immutable: You can't change the day or time of a calendar date once it is created (although you can change its format string and its time zone). Because it is basically immutable, many objects often share a single calendar date object. There is seldom any need to create a copy of an NSCalendarDate
object.
Here are some of the commonly used methods implemented by NSCalendarDate
:
+ (id)calendarDate
This method creates and returns a calendar date initialized to the current date and time in the default format for the locale. The time zone will be the time zone to which the machine is set. Remember that the returned object is autoreleased.
This is a class method. A class method is triggered by sending a message to the class instead of an instance. This one, for example, could be used as follows:
NSCalendarDate *now; now = [NSCalendarDate calendarDate];
In the interface file, implementation file, and documentation, class methods are recognizable because they start with “+” instead of “-”.
+ (id)dateWithYear:(int)year month:(unsigned)month day:(unsigned)day hour:(unsigned)hour minute:(unsigned)minute second:(unsigned)second timeZone:(NSTimeZone *)aTimeZone
This class method returns an autoreleased object. Specifically, it creates and returns a calendar date initialized with the specified values. The year value must include the century (for example, 2001 instead of 1). The other values are the standard ones: 1 through 12 for months, 1 through 31 for days, 0 through 23 for hours, and 0 through 59 for both minutes and seconds. The following code fragment shows a calendar date created with a date on 3 August 2000, 4 P.M., Pacific Standard Time (timeZoneWithName:
returns the NSTimeZone
object that represents the time zone with the specified name):
NSTimeZone *pacific = [NSTimeZone timeZoneWithName:@"PST"] NSCalendarDate *hotTime = [NSCalendarDate dateWithYear:2000 month:8 day:3 hour:16 minute:0 second:0 timeZone:pacific]; - (NSCalendarDate *)dateByAddingYears:(int)year months:(int)month days:(int)day hours:(int)hour minutes:(int)minute seconds:(int)second
This method returns a calendar date with the year, month, day, hour, minute, and second offsets specified as arguments. A positive offset is the future, and a negative offset represents the past. You used this method in main.m
. Here, we are creating a day six months after hotTime
:
NSCalendarDate *coldTime = [hotTime dateByAddingYears:0
months:6
days:0
hours:0
minutes:0
seconds:0];
- (int)dayOfCommonEra
This method returns the number of days since the beginning of 1 A.D.
- (int)dayOfMonth
This method returns a number that indicates the day of the month (1 through 31) of the receiver.
- (int)dayOfWeek
This method returns a number that indicates the day of the week (0 through 6) of the receiver, where 0 indicates Sunday.
- (int)dayOfYear
This method returns a number that indicates the day of the year (1 through 366) of the receiver.
- (int)hourOfDay
This method returns the hour value (0 through 23) of the receiver.
- (int)minuteOfHour
This method returns the minutes value (0 through 59) of the receiver.
- (int)monthOfYear
This method returns a number that indicates the month of the year (1 through 12) of the receiver.
- (void)setCalendarFormat:(NSString *)format
Table 3.2. Possible Tokens in the Calendar Format String
Symbol | Meaning |
---|---|
%y | Year without century (00–99) |
%Y | Year with century (“1990”) |
%b | Abbreviated month name (“Jan”) |
%B | Full month name (“January”) |
%m | Month as a decimal number (01–12) |
%a | Abbreviated weekday name (“Fri”) |
%A | Full weekday name (“Friday”) |
%w | Weekday as a decimal number (0–6), where Sunday is 0 |
%d | Day of the month as a decimal number (01–31) |
%e | Same as %d but does not print the leading 0 |
%j | Day of the year as a decimal number (001–366) |
%H | Hour based on a 24-hour clock as a decimal number (00–23) |
%I | Hour based on a 12-hour clock as a decimal number (01–12) |
%p | A.M./P.M. designation for the locale |
%M | Minute as a decimal number (00–59) |
%S | Second as a decimal number (00–59) |
%F | Milliseconds as a decimal number (000–999) |
%x | Date using the date representation for the locale |
%X | Time using the time representation for the locale |
%c | Shorthand for %X %x, the locale format for date and time |
%Z | Time zone name (“EST”) |
%z | Time zone offset in hours and minutes from GMT (HHMM) |
%% | A “%” character |
This method sets the default calendar format for the receiver. A calendar format is a string formatted with date-conversion specifiers, as given in Table 3.2.
- (NSDate *)laterDate:(NSDate *)anotherDate
This method is inherited from NSDate
. It compares the receiver to anotherDate
and returns the later of the two.
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate
This method returns the interval in seconds between the receiver and anotherDate
. If the receiver is earlier than anotherDate
, the return value is negative. NSTimeInterval
is the same as double
.
Notice the following lines in your main
function:
newEntry = [[LotteryEntry alloc] init]; [newEntry prepareRandomNumbers];
You are creating a new instance and then immediately calling prepareRandomNumbers
to initialize firstNumber
and secondNumber
. This is something that should really be handled by the initializer, so you are going to override the init
method in your LotteryEntry
class.
In the LotteryEntry.m
file, change the method prepareRandomNumbers
into an init
method:
- (id)init { [super init]; firstNumber = random() % 100 + 1; secondNumber = random() % 100 + 1; return self; }
The init
method calls the superclass's initializer at the beginning, initializes its own variables, and then returns self
.
Now delete the following line in main.m
:
[newEntry prepareRandomNumbers];
In LotteryEntry.h
, delete the following declaration:
- (void)prepareRandomNumbers;
Build and run your program to reassure yourself that it still works.
A few of the initializers in Cocoa will return nil
if initialization was impossible. Also, some initializers in Cocoa return an object that is not the receiver. If a programmer is worried that the superclass's initializer may be one of these cases, he will create an initializer that is something like this:
- (id)init { self = [super init]; if (self != nil) { [self setFirstNumber:random() % 100 + 1]; [self setSecondNumber:random() % 100 + 1]; } return self; }
This version will always work and is considered the most correct form; however, none of the classes that you will subclass in this book require these checks. For simplicity, this book will sometimes leave out the checks.
Look at the same place in main.m
. It should now look like this:
newEntry = [[LotteryEntry alloc] init]; [newEntry setEntryDate:[now dateByAddingYears:0 months:0 days:(i * 7) hours:0 minutes:0 seconds:0]];
It might be nicer if you could supply the date as an argument to the initializer. Change those lines to look like this:
newEntry = [[LotteryEntry alloc] initWithEntryDate: [now dateByAddingYears:0 months:0 days:(i * 7) hours:0 minutes:0 seconds:0]];
First, declare the method in LotteryEntry.h
:
- (id)initWithEntryDate:(NSCalendarDate *)theDate;
Now, change the initializer:
- (id)initWithEntryDate:(NSCalendarDate *)theDate { [super init]; [self setEntryDate:theDate]; firstNumber = random() % 100 + 1; secondNumber = random() % 100 + 1; return self; }
Build and run your program. It should work correctly.
However, your class LotteryEntry
has a problem. You are going to e-mail the class to your friend Rex. Rex plans to use the class LotteryEntry
in his program but might not realize that you have written initWithEntryDate:
. If he made this mistake, he might write the following lines of code:
NSCalendarDate *today = [NSCalendarDate calendarDate]; LotteryEntry *bigWin = [[LotteryEntry alloc] init]; [bigWin setEntryDate:today];
This code will not create an error. Instead, it will simply go up the inheritance tree until it finds NSObject
's init
method. The problem is that firstNumber
and secondNumber
will not get initialized properly—both will be zero.
To protect Rex from his own ignorance, you will override init
to call your initializer with a default date:
- (id)init { return [self initWithEntryDate:[NSCalendarDate calendarDate]]; }
Add this method to your LotteryEntry.m
file.
Notice that initWithEntryDate:
still does all the work. Because a class can have multiple initializers, we call the one that does the work the designated initializer. If a class has several initializers, the designated initializer typically takes the most arguments. You should clearly document which of your initializers is the designated initializer. Note that the designated initializer for NSObject
is init
.
Conventions for Creating Initializers (rules that Cocoa programmers try to follow regarding initializers):
You do not have to create any initializer in your class if the superclass's initializers are sufficient.
If you decide to create an initializer, you must override the superclass's designated initializer.
If you create multiple initializers, only one does the work—the designated initializer. All other initializers call the designated initializer.
The designated initializer of your class will call its superclass's designated initializer.
The Free Software Foundation developed the compiler (gcc) and the debugger (gdb) that come with Apple's developer tools. NeXT and Apple have made significant improvements to both over the years. This section discusses the processes of setting breakpoints, invoking the debugger, and browsing the values of variables.
While browsing code, you may have noticed a gray margin to the left of your code. If you click in that margin, a breakpoint will be added at the corresponding line. Add a breakpoint in main.m
at the following line (Figure 3.15):
[array addObject:newEntry];
If your program is compiled, you can invoke the debugger by clicking on the button that looks like a can of bug spray in Xcode
. The debugger will take a few seconds to get started, and then it will run your program until it hits the breakpoint (Figure 3.16).
In the list on the left, you can see the frames on the stack. Because our breakpoint is in main()
, the stack is not very deep. In the outline view on the right, you can see the variables and their values. Note that the variable i
is currently 0.
The buttons above the stack information are for pausing, continuing, and stepping over, into, and out of functions. Click the continue button to execute another iteration of the loop. Click the step-over button to walk through the code line by line.
The gdb debugger, being a Unix thing, is usually run from a terminal. To see the terminal-like view of the gdb process, click on the tab labeled Console (Figure 3.17).
In the console, you have full access to all of gdb's capabilities. One very handy feature is “print-object” (“po”). If a variable is a pointer to an object, when you “po” it, the object is sent the message description
and the result is printed in the console. Try printing the newEntry
variable.
po newEntry
You should see the result of your description
method (Figure 3.18). (If you get a warning message from the debugger, ignore it.)
One amazing and nifty feature of Xcode is “fix and continue.” As an example, imagine for a moment that the lottery in your town is run every two weeks instead of every week. While the application is running in the debugger, you can change code and see the new behavior without restarting the app. Try it. Change the line to say
newEntry = [[LotteryEntry alloc] initWithEntryDate:
[now dateByAddingYears:0
months:0
days:(i * 14)
hours:0
minutes:0
seconds:0]];
Click the Fix
toolbar item. When that is complete, click the Continue
toolbar item several times. The new entries should be two weeks apart.
To remove the breakpoint, just drag it out of the margin. Remove your breakpoint and put another one in LotteryEntry
's dealloc
method next to the line that reads
NSLog(@"Destroying %@", self);
Click the continue button to run the application until the first LotteryEntry
object executes its dealloc
method. Notice that the stack is deeper this time. You can choose a frame in the stack in the list. The variables for that frame will appear in the outline view on the right.
Many things that go wrong create an instance of NSException
and send it the message raise
. For this reason, is a good idea to always have a breakpoint on NSException
's raise
method. The easiest way to do this for all your projects is to create a .gdbinit
file in your home directory (/Users/YourNameHere/.gdbinit
). This file is read by gdb every time it launches. Using TextEdit, create a file containing this line:
fb -[NSException raise]
You can test this breakpoint by changing the second loop to overrun the limits of the array in main()
:
for (i = 0; i < 11; i++) {
entryToPrint = [array objectAtIndex:i];
Remove your other breakpoints and restart the debugger. Your program should stop when the exception is raised.
You can also add such a breakpoint in Xcode. Bring up the Breakpoints window, and then click the New Breakpoint button. In the new line, type
-[NSException raise]
as shown in Figure 3.18.
That's enough to get you started with the debugger. For more in-depth information, refer to the documentation from the Free Software Foundation (
http://www.gnu.org/)
.
You have written a simple program in Objective-C, including a main()
function that created several objects. Some of these objects were instances of LotteryEntry
, a class that you created. The program logged some information to the console.
At this point, you have a fairly complete understanding of Objective-C. Objective-C is not a complex language. The rest of the book is concerned with the frameworks that make up Cocoa. From now on, you will be creating event-driven applications, not command-line tools.
As mentioned earlier, an object is like a C struct. NSObject
declares an instance variable called isa
. Because NSObject
is the root of the entire class inheritance tree, every object has an isa
pointer to the class structure that created the object (Figure 3.19). The class structure includes the names and types of the instance variables for the class. It also has the implementation of the class's methods. The class structure has a pointer to the class structure for its superclass.
The methods are indexed by the selector. The selector is of type SEL
. Although, SEL
is actually defined to be char *
, it is most useful to think of it as an int
. Each method name is mapped to a unique int
. For example, the method name addObject:
might map to the number 12. When you look up methods, you will use the selector, not the string @"addObject:"
.
As part of the Objective-C data structures, a table maps the names of methods to their selectors. Figure 3.20 shows an example.
At compile time, the compiler looks up the selectors wherever it sees a message send. Thus,
[myObject addObject:yourObject];
becomes (assuming the selector for addObject:
is 12)
objc_msgSend(myObject, 12, yourObject);
Here objc_msgSend()
looks at myObject
's isa
pointer to get to its class structure and looks for the method associated with 12. If it does not find the method, it follows the pointer to the superclass. If the superclass does not have a method for 12, it continues searching up the tree. If it reaches the top of the tree without finding a method, the function throws an exception.
Clearly, this is a very dynamic way of handling messages. These class structures can be changed at runtime. In particular, using the NSBundle
class, it is relatively easy to add classes and methods to your program while it is running. This very powerful technique has been used to create applications that can be extended by other developers.