Cocoa memory management for smarties, Part 1: release me not

If I were to offer one bit of advice to young programmers, I would say always … uh … never … forget to check your references. Not to mention your optics and your closet. Supposedly, the rules of Cocoa memory management are simple. Just set GCC_ENABLE_OBJC_GC = required and wait for the money to roll in, right? For those who don’t enjoy the smell of Objective-C garbage collection, we have the tried-and-true manual rules. If you obtain a reference to an object by calling +alloc, +allocWithZone:, +new, or -copy, then the reference should remain valid indefinitely. A reference obtained in any other way is guaranteed to be valid only during the current event loop, so you’ll need to call -retain if the reference should remain valid for longer (e.g., if you want to store it in an instance variable). Each of the aforementioned method calls must be balanced at some point by a call to -release, if it’s safe for the object to deallocate immediately, or -autorelease, if you need the object to stick around until the end of the event loop.

In theory, these rules are foolproof. In practice, however, they’re not even expert-proof, as we see when the experts’ apps unexpectedly quit. Thus, I believe that writing a series of posts exploring the subtleties of Cocoa memory management is a worthy endeavor. Moreover, I need to kill some time before football season kicks off.

Below is a simplified version of a crasher I once discovered and fixed. I’m only giving the bare essentials here. There was a lot of code involved, which made it more difficult to diagnose.


@implementation MYChild

-(void)doSomething
{
	...
	if (...)
	{
		[self setMyProperty:theValue];
	}
	if ([_myArray count] > 0)
	...
}

@end

@implementation MYParent

-(void)start
{
	...
	_myChild = [[MYChild alloc] init];
	[_myChild addObserver:self forKeyPath:@"myProperty" options:0 context:NULL];
	...
}

-(void)stop
{
	...
	[_myChild removeObserver:self forKeyPath:@"myProperty"];
	[_myChild release];
	_myChild = nil;
	...
}

-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
	...
	[self stop];
	...
}

@end

Can you guess where the crash occurred? The particularly tricky part is that observeValueForKeyPath: is called by the Cocoa runtime rather than by the app’s own code, which means that if you’re not already aware of the key-value observer, the cause of the crash is rather mystifying.

My solution, by the way, was to replace [_myChild release] with [_myChild autorelease]. Ideally, you would probably access all ivars via getters and setters, but that’s a topic for a later post in this series.

In light of the above example, let’s now attempt to add to the rules of memory management:

  • Rules are for suckers, simpletons, and lawyers.
  • No method is safe, only call paths are safe. You can’t look at a method in isolation and determine that it handles memory management properly. On the other hand, if you’re doing object-oriented encapsulation correctly, you shouldn’t have to worry about the implementation of other objects. The proper unit of analysis, I believe, is the entire call path within the object’s implementation.
  • Don’t release any method arguments. It’s almost always possible for the caller of aMethod: to have code such as { [anObject aMethod:anArgument]; [anArgument anotherMethod]; }, so don’t invalidate anArgument during the current event loop. This is the reason why -autorelease was invented. (Also, it would be pretty silly to have NSAutoreleasePool without anything to go in the pool.

As for the jello, I can explain. I was hungry and preparing for gravity to reverse.

2 Responses to “Cocoa memory management for smarties, Part 1: release me not”

  1. Jeff says:

    The list of +alloc, +allocWithZone:, +new, and -copy should also include -copyWithZone:, -mutableCopy, and -mutableCopyWithZone:.

  2. [...] part 1 of this series I talked about Cocoa memory management. For a summary of that post, load it in your browser, select [...]