Cocoa memory management for smarties, Part 2: working with a nib

Well, it’s back to work for the moment. I was offered the VP slot, but I had to turn it down because they refused to accept the NDA: my name would be on the ticket, but nobody could talk about me publicly. Also, they wouldn’t get rid of the brown M&Ms. I am waiting for an offer by email from another guy, but I don’t hold out much hope, since I’m also waiting for him to learn how to use email.

In part 1 of this series I talked about Cocoa memory management. For a summary of that post, load it in your browser, select all, and pass it to the Summarize system service. And now for something completely the same, in part 2 I’m going to talk about Cocoa memory management. As you all know, I love nibs. I find them to be a very human-friendly interface, much like the gom jabbar. A nib file contains archived Objective-C objects that are instantiated when the nib is instantiated, so their memory needs to be managed just as objects instantiated programmatically. Fortunately, if the nib File’s Owner is an NSWindowController or (new in Leopard) NSViewController, the File’s Owner takes care of the memory management automatically. Otherwise, it’s your job.

According to the documentation, Objects in a nib file are initially created with a retain count of 1. As it rebuilds the object hierarchy, however, Cocoa autoreleases any objects that have a parent or owning object, such as views nested inside view hierarchies. By the time the nib-loading code is complete, only the top-level objects in the nib file have a positive retain count and no owning object. This makes the top-level objects a potential memory leak if your code does not assume responsibility for them. If you look at your nib with the icon view in Interface Builder, the top-level objects consist of everything except the proxy objects such as File’s Owner, First Responder, and (new in Leopard) Application. Thus, top-level objects include not only windows but also contextual menus, array controllers, etc. For example, Vienna’s MainMenu.nib, pictured below, contains 14 top-level objects. (Kids, don’t try this at home.)

MainMenu.nib

By the way, the MainMenu.nib file, or whatever is specified by NSMainNibFile in Info.plist, is actually a special case, because the File’s Owner is NSApplication (or whatever is specified by NSPrincipalClass). You don’t need to worry about its memory management either, although the objects in the nib will usually remain in memory for the lifetime of the app. On termination, NSApplication may or may not choose to release those objects, as it pleases, so don’t depend on any code in dealloc. Cleaning up memory on app termination is like sweeping your floor right before a tornado hits.

In order to manage the memory of the nib’s top-level objects, you’ll need a reference to them. The method -[NSNib instantiateNibWithOwner: topLevelObjects:] is handy in this respect. The documentation of the (NSArray **) topLevelObjects argument may be a little confusing, though: On input, a variable capable of holding an NSArray object. On output, this variable contains an autoreleased NSArray object containing the top-level objects from the nib file. Although the NSArray is autoreleased, the top-level objects themselves are not. Each object in the NSArray will have a retain count of at least 2. Thus, after the NSArray is deallocated and releases its objects, they will each still have a retain count of at least 1. A typical way to handle this is as follows.


@interface MyObject : NSObject
{ NSArray * _topLevelObjects; }
@end

@implementation MyObject

-(id) init
{
	self = [super init];
	if (self)
	{
		NSNib * nib = [[[NSNib alloc] initWithNibNamed:@"MyNib" bundle:nil] autorelease];
		if (nib && [nib instantiateNibWithOwner:self topLevelObjects:&_topLevelObjects])
		{
			// The array is autoreleased, so we need to retain it.
			// Release the objects now so that they'll be deallocated along with the array.
			[[_topLevelObjects retain] makeObjectsPerformSelector:@selector(release)];
		}
	}
	return self;
}

-(void) dealloc
{
	[_topLevelObjects release];
	[super dealloc];
}

@end

If you use a different method to instantiate the nib, such as +[NSBundle loadNibNamed:owner:], or if you don’t want to keep an array of the top-level objects, you’ll need to create an IBOutlet for each top-level object in the nib and release the objects in dealloc along with any other ivars.

A major caveat for do-it-yourself nib loading is that even if you take care to release your top-level objects, you can still get a memory leak if you use Cocoa bindings in your nib. This occurs when an object in the nib binds to the File’s Owner. The File’s Owner never gets deallocated in this case, and thus neither do the top-level objects. Again, NSWindowController and NSViewController already take care of this problem automatically, but you’ll need to deal with it if the File’s Owner is not one of those classes or their subclasses. Basically, there are three possible solutions:

  1. Unbind the bindings programmatically. You’ll need to do this elsewhere than the File Owner’s dealloc, which won’t get called unless you unbind.
  2. Bind to a different object in the nib rather than to the File’s Owner.
  3. Don’t use Cocoa bindings and nibs, because they’re teh suck.

4 Responses to “Cocoa memory management for smarties, Part 2: working with a nib”

  1. Peter Hosey says:

    Basically, there are three possible solutions:

    2. Bind to a different object in the nib rather than to the File’s Owner.

    Sounds like the reason I’ve been looking for to use NSObjectController.

  2. Jeff says:

    Sure, as long as you don’t bind the controller’s content to File’s Owner. :-)

  3. Peter Hosey says:

    Jeff: Generally, you’d use an outlet for that, rather than a binding. If you want to use the value of a property of the FO, rather than the FO itself: Two object controllers.

  4. Chris Simmons says:

    Thanks for this article, this confirms my findings. I was unable to find this information anywhere in apple’s documentation. Do you know of any apple written docs that state this?

    +[NSBundle loadNibNamed:owner:]
    -[NSNib instantiateNibWithOwner: topLevelObjects:]
    The documentation of these two methods should really mention this fact.