Nat! bio photo

Nat!

Senior Mull.

Twitter RSS

Github

Nibs and the retainCounts of Objects therein

If you instantiate a top level object in a Nib and you haven't connected it to anything else, the retainCount after load will be 1 and will stay that way.

IBOutlet id   outlet1;
IBOutlet id   outlet2;

If you do the same and connect it to another object, like maybe to the outlet1 instance variable of Files Owner , the retainCount will still be 1! So if you hook up the same instance twice to Files Owner (outlet1, outlet2) you still only have to release it once on the deallocation of Files Owner. The same goes for every other top level object inside the Nib. Non top level objects, do not have that extra retainCount. So if you hook up Files Owner to a view inside a window, you don't release that view.

The big exception is if the destination's outlet is driven by standard accessor methods, which does retain counting. In that case by virtue of the accessor the retainCount will be 2 for top level objects. This is a bit of a mess.

If you don't have any accessors you can deal with this somewhat elegantly by grabbing all toplevel objects off a Nib

   ...
   _toplevel = [NSMutableArray new];
   context  = [NSDictionary dictionaryWithObjectsAndKeys:
      self, @"NSOwner",
      toplevel, @"NSTopLevelObjects",
      nil];
   flag = [bundle loadNibFile:nibFileName
            externalNameTable:context
                     withZone:[self zone]];
   ...

so in the end you release the toplevel array and also release the contained objects one more time.

- (void) dealloc
{
   [_toplevel makeObjectsPerformSelector:@selector( release)];
   [_toplevel release];
   ...

You will run into trouble if some of the windows in the Nib have the releaseOnClose flag set. This function may be useful to weed out the problematic objects before before retaining the toplevel array in an instance variable. Doing the weeding in dealloc is too late, very early in -awakeFromNib seems to be a good idea:

- (void) setTopLevelObjects:(NSArray *) candidates
{
   unsigned int   i, n;
   id             p;
   Class          nsWindowClass;
   
   NSParameterAssert( ! _toplevel);
   _toplevel = [NSMutableArray new];
   
   nsWindowClass = [NSWindow class];
   n = [candidates count];
   for( i = 0; i < n; i++)
   {
      p = [candidates objectAtIndex:i];
      if( [p isKindOfClass:nsWindowClass])
         if( [p isReleasedWhenClosed])
         {
            if( ! [p isVisible]) // assume this meant "visible at launch time")
               NSLog( @"%@ will bring you grief and sorrow", p);
            continue;
         }
      [_toplevel addObject:p];
   }
}

And then again it may not. Because this isn't 100% fool proof. One may want to consider if the window isOneShot also and maybe some other things as well ? It really depends on what you want to do with the windows, and when you want them to dealloc.

Otherwise you need to wire all toplevel objects to your NSOwner (handy connectors inside the Nib will place them there) and release them manually sometime. You have to be careful about connected objects that have been placed there with the use of an accessor, unless you inherited the accessor. They need to be released once more. So it would be:

- (void) dealloc
{
    [self setStupido:nil];
    [stupido release];
    ...
}

Is it obvious, that I haven't read any of the fine recent Cocoa books ? I hope not :)