Chapter 3. Objective-C

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.

Creating and Using Instances

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):

[foo addObject:bar];

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.

Using Existing Classes

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).

Choose Project Type

Figure 3.1. Choose Project Type

Name the project lottery (Figure 3.2). Unlike the names of applications, most tool names are lowercase.

Name Project

Figure 3.2. Name Project

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.

main.m

Figure 3.3. main.m

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 of Number Objects

Figure 3.4. The Array of Number Objects

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

NoteIf 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 NSStrings, 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 NSStrings:

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).

Completed Execution

Figure 3.5. Completed Execution

Memory Management: Retain Count, Releasing, and Retaining

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).

Objects Retain Each Other

Figure 3.6. Objects Retain Each Other

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]; 
}

Sending Messages to nil

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.

NSObject, NSArray, NSMutableArray, and NSString

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.

Inheritance Diagram

Figure 3.7. Inheritance Diagram

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

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.

    - (NSString *)description
    
  • 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

        NSLog(@"The number at index %d is %@", i,
                                 [numberToPrint description]);
    
    - (id)retain
    
  • Increments the receiver's retain count.

    - (void)release
    
  • Decrements the receiver's retain count and sends it a dealloc message if its retain count becomes zero.

    - (void)dealloc
    
  • 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.

NSArray

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.

    - (id)lastObject
    
  • Returns the object in the array with the highest index value. If the array is empty, nil is returned.

    - (BOOL)containsObject:(id)anObject
    
  • 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.

    - (unsigned)indexOfObject:(id)anObject
    
  • 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

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:

  • - (void)addObject:(id)anObject
    
  • 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]];

NSString

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];
    

“Inherits from” Versus “Uses” or “Knows About”

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.

Creating Your Own Classes

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.

Choose Type of File

Figure 3.9. Choose Type of File

Completed Program

Figure 3.8. Completed Program

Creating the LotteryEntry Class

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 File

Figure 3.10. Name File

Name the file LotteryEntry.m (Figure 3.11). Note that you are also causing LotteryEntry.h to be created.

Object Diagram

Figure 3.11. Object Diagram

LotteryEntry.h

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 ints.

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.)

LotteryEntry.m

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.

dealloc

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 ints. 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.

Changing main.m

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.

Implementing a description Method

Build and run your application. You should see something like Figure 3.12.

Completed Execution

Figure 3.12. Completed Execution

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.

Creating Autoreleased Objects

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).

Completed Execution

Figure 3.13. Completed Execution

Temporary Objects

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.)

Accessor Methods

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.

Before and After setFoo:

Figure 3.14. Before and After setFoo:

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.

NSCalendarDate

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.

Writing Initializers

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.

Initializers with Arguments

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 Debugger

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];
Creating a Breakpoint

Figure 3.15. Creating a Breakpoint

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).

Starting the Debugger

Figure 3.16. Starting the Debugger

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).

Stopped at a Breakpoint

Figure 3.17. Stopped at a Breakpoint

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.)

Adding a Breakpoint

Figure 3.18. Adding a Breakpoint

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/).

What Have You Done?

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.

For the More Curious: How Does Messaging Work?

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.

Each Object Has a Pointer to Its Class

Figure 3.19. Each Object Has a Pointer to Its Class

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.

The Selector Table

Figure 3.20. The Selector Table

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.

Challenge

Change the format string on the calendar date objects in your LotteryEntry class.

Create another loop in main() that removes the LotteryEntry objects from the array one-by-one. Notice that the objects are deallocated as they are removed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset