Cocoa Programming for Mac OS X - Aaron Hillegass [27]
So the LotteryEntry object is being leaked (it is never 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 tidy Objective-C programmers, let’s fix the code by releasing the object after we add it to the array.
The revised loop should look like this:
LotteryEntry *newEntry = [[LotteryEntry alloc]
initWithEntryDate:iWeeksFromNow];
[array addObject:newEntry];
[newEntry release];
}
We would say that “array now has ownership of newEntry.”
Perhaps you are beginning to think more critically about object ownership and lifetime. Consider LotteryEntry’s instance variable, entryDate. What guarantee does LotteryEntry have that the NSDate instance will not be deallocated out from under it?
Right now, it has no guarantee. That’s why it’s very common for objects to retain objects they hold references to. This is called a strong reference. In some situations, a weak reference (nonretaining) is more appropriate; this is generally used to avoid creating a retain cycle. For example, a parent object generally retains its children (perhaps indirectly via an array), but the child will not retain its parent.
Open LotteryEntry.m and modify the initializer to retain entryDate:
- (id)initWithEntryDate:(NSDate *)theDate
{
self = [super init];
if (self) {
entryDate = [theDate retain];
firstNumber = ((int)random() % 100) + 1;
secondNumber = ((int)random() % 100) + 1;
}
return self;
}
Now that it has a strong reference to entryDate, LotteryEntry must release it when it no longer needs it. The perfect place to relinquish ownership is dealloc.
dealloc
When an object with a retain count of 1 is sent release, the dealloc method will be called. An object’s dealloc method must release any objects that it was retaining and then call the superclass’s dealloc method. Add a dealloc method to LotteryEntry.m:
- (void)dealloc
{
NSLog(@"deallocating %@", self);
[entryDate release];
[super dealloc];
}
Note that we always call [super dealloc] at the end of an implementation of dealloc.
Build and run your app. It should work fine, and you should see that the entries are being properly deallocated (Figure 4.4).
Figure 4.4. Running without the Garbage Collector
There is, however, still a memory leak.
Autoreleasing Objects
In the previous chapter, you created a description method that looks like this:
- (NSString *)description
{
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeStyle:NSDateFormatterNoStyle];
[df setDateStyle:NSDateFormatterMediumStyle];
NSString *result;
result = [[NSString alloc] initWithFormat:@"%@ = %d and %d",
[df stringFromDate:entryDate],
firstNumber, secondNumber];
return result;
}
This code works perfectly well but results in an annoying memory leak. When the method returns, df and result both have a retain count of 1.
We might attempt to fix this leak with something like this:
- (NSString *)description
{
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeStyle:NSDateFormatterNoStyle];
[df setDateStyle:NSDateFormatterMediumStyle];
NSString *result;
result = [[NSString alloc] initWithFormat:@"%@ = %d and %d",
[df stringFromDate:entryDate],
firstNumber, secondNumber];
[result release];
[df release];
return result;
}
This change handles the date formatter just fine, as it is no longer needed, but creates a different problem with the string. When sent the release message, the string’s retain count would go to zero, and the string would be deallocated. The pointer returned by this method would be to the freed object, now an invalid pointer, almost certainly leading to a crash.
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 the final piece of the retain-count