posted by Jared White on Wed 7th May 2003 07:13 UTC

"Page 3"

With the MyPerson interface out of the way, we arrive at the implementation file: MyPerson.m. Let's take it bit by bit this time. The first bit might look like this:

#import "MyPerson.h"

@implementation MyPerson

- (id)init
    self = [super init];
    if (self) {
        fullName = @"";
        address = @"";
        dateOfBirth = [[NSCalendarDate calendarDate] retain];
        hairColor = [[NSColor clearColor] retain];
    return self;

The first line is an import directive that says to import the counterpart header file where the object's interface is declared. If the interface weren't imported, then the object implementation wouldn't know what the heck it's implementing!

Next we have a simple init method. As presented in Part 1, the first order of business to send a message to the superclass (NSObject) to initialize the object, and the returned object pointer is assigned to self. Now the next step (if (self) {) is theoretically optional. It's very unlikely that the NSObject initialization process will fail, but, just in case, we'll start assigning values (we'll call them values, but they're really only pointers to objects containing values) to the instance variables only if self equals anything other than nil (nil is an Objective-C data type representing a null pointer, a "non-object" if you will).

We then assign blank NSString values (really, pointers to the newly-created string objects) to fullName and address. dateOfBirth is assigned an NSCalendarDate value representing the very point in time when that statement is executed. In other words, "now". The hairColor variable is assigned an NSColor value representing a no color value (a clear color). Note that both of these messages are each nested in a retain message. You'll learn all about that in a moment. Hmm, when you sum it up, basically this person has no name, no address, was just born, and has transparent hair. Pretty freaky, isn't it?

But wait! That's what the accessor methods are for: to assign "real" values to these variables! Here's the code for them:

- (void)setFullname:(NSString *)newName
    [newName retain];
    [fullName release];
    fullName = newName;
- (NSString *)fullname { return fullName; }

- (void)setAddress:(NSString *)newAddress
    [newAddress retain];
    [address release];
    address = newAddress;
- (NSString *)address { return address; }

- (void)setDateOfBirth:(NSCalendarDate *)newDate
    [newDate retain];
    [dateOfBirth release];
    dateOfBirth = newDate;
- (NSCalendarDate *)dateOfBirth { return dateOfBirth; }

- (void)setHairColor:(NSColor *)newColor
    [newColor retain];
    [hairColor release];
    hairColor = newColor;
- (NSColor *)hairColor { return hairColor; }

Looks scary, doesn't it! What's with all that retain and release jargon? Only one of the most important concepts to understand about the Cocoa framework: memory management. The bane of the programming world, memory management is one of those things that most people just wish they could ignore and forget about. And, in some languages, fancy run-time engine techniques such as "garbage collection" really do allow people to forget about it most of the time. While Cocoa doesn't use garbage collection, it uses another kind of memory management that is by all accounts pretty simple as well, and it's typically called "retain and release". This is how it works:

Object A (we won't discuss how Object A was created) allocates and initializes Object B. Object B automatically starts out with a retain count of 1. In other words, Object B is retained by Object A. Now let's say that Object A no longer needs to use Object B, maybe because Object A was "deallocated" (in other words, it no longer exists in memory). What happens to Object B? I'm afraid it just sits around in memory forever (or until your application quits) and is completely useless because there are no more pointers to it anywhere in your application. This is called a memory leak, and memory leaks are bad because lots of big memory leaks will drastically slow down the host computer and make your users angry. And angry users are to be avoided at all costs, believe me.

What needs to happen is that Object B is released when Object A no longer needs to use it. This is what the [objectB release]; code is for. Releasing an object doesn't automatically deallocate it however. An object is deallocated only when its retain count goes down to 0. When an object has a retain count of 1, and it's sent a release message, its retain count will change to 0 and it will be deallocated. But what if there's another object, Object C, that is passed a pointer to Object B from Object A? What if Object A releases Object B, and then Object C tries to use Object B? What will happen? Your application will crash. Crash and burn, baby. That will make your users even more angry, and you wouldn't want that! So, basically, Object C should send a retain message to Object B so that it will still be in memory when Object A releases it. In other words, Object B's retain count will go up to 2, so that when it's released by Object A, its retain count will go down to just 1 and it will still be in memory and available for use.

So, you see, it's really quite simple. Every object initialization or retain message should be balanced by a release message. In other words, 1 + 1 + 1 - 1 - 1 - 1 = 0. There's a catch here, however. Sometimes you're going to lose a pointer to an object before you have had a chance to release it (maybe because you bypassed assigning the pointer to a variable). Sometimes you're going to create an object and then immediately pass the pointer along to another object that isn't guaranteed to release the object. In these cases, there is no clear solution based on a simple release/retain mechanism. That's why Cocoa provides an ingenious solution: an autorelease pool. The idea is that a "third-party object" (an autorelease pool) will retain an object for a short period of time (normally one loop of an application's "event cycle") and then release it. These kinds of objects are usually considered "temporary" objects, that is, they are used in situations where objects need to be used only for a short period of time. The way you autorelease an object (i. e., add it to the current autorelease pool) is simply by sending it an autorelease message rather than a release message. Let's break this down again:

Object A creates Object B (which has a retain count of 1), then autoreleases it and hands it over to Object C, which uses it in just one method. Shortly after that method has been executed, the autorelease pool sends Object B a release message which brings the retain count down to 0 and Object B is deallocated.

The really nice thing about the autorelease pool is that if an object's retain count is above 1, then it won't be deallocated when the autorelease pool sends it a release message. So you can autorelease an object with a retain count above 1, knowing that it won't be deallocated prematurely. And you can send an object a retain message after it's been autoreleased, so that it will be usable after it's been released by the autorelease pool. That's actually something to be very careful about when using autorelease pools: you don't want an object to be released by the autorelease pool before you're done using it, because then your application will crash. So always retain an object if you want to make sure it won't be autoreleased before you're done using it.

By the way, you might be interested to know that autorelease pools are actually Cocoa objects belonging to the class NSAutoreleasePool, and you can create more than one autorelease pool during the duration of your application. However, regular GUI-based Cocoa applications have a default autorelease pool created automatically, so you don't have to worry about creating one yourself.

Going back to MyPerson's accessor methods, you can see that each of the "set" methods sends a retain message to the new object, sends a release method to the old object, and then makes the instance variable point to the new object. Notice the retain, then release order here. This is the best order, because if the object being pointed to by the method argument is the exact same object that the instance variable points to, it's retain count will just go up 1 and then down 1, which is like nothing happened. Think what would happen if the object were released, then retained. If it were released with a retain count of one, it would be deallocated, and the retain message would then be sent to a invalid piece of memory, causing your application to crash. So retain, then release, is the only order that can be used if you use this style of coding (there are, in fact, several other methodologies different people use to get around this problem, but I personally like the one outlined above the best).

One more thing that I'd like to mention is, if you look back at the init method for MyPerson, you'll see that the new date and color objects are both being sent a retain message. Why? Well, the convention in Cocoa is that any "convenience" class methods that automatically allocate and initialize an object for you also autorelease the object before returning it. So, if you want to make sure the object isn't deallocated when it's released by the autorelease pool, you have to send it a retain message, which is what we're doing in our init method.

Now the accessor methods which are for retrieving a value are considerably easier. They fit on just one line, in fact, because all the code does is return the value. Easy, no?

Table of contents
  1. "Page 1"
  2. "Page 2"
  3. "Page 3"
  4. "Page 4"
  5. "Page 5"
e p (0)    24 Comment(s)

Technology White Papers

See More