Q: NSViewController on Mac OS X 10.4 I am working on a bundle code base—not an app—where development started on 10.4, and is required to run on 10.4, but works all the way up to 10.8. It loads its views from nib files manually and I only just recently realised that there is a substantial memory leak because the nibs utilise bindings and bind against the file's owner, creating reference cycles and preventing the file's owner class from deallocating. I think this is made worse by having the "file's owner" load its own nib.
I load nibs using the following code (this code is in a base class and subclasses override +nibName):
NSString *nibName = [[self class] nibName];
NSNib *nib = [[NSNib alloc] initWithNibNamed:nibName bundle:myBundle];
[nib instantiateNibWithOwner:self topLevelObjects:&topLevelObjects];

Since I have to target 10.4, I am unable to use NSViewController. I think I'll need to implement my own view controller class, but how do I prevent the references cycles from happening as the NSViewController class promises to do? If the view controller is the nib's "file's owner" won't I instead just be pushing the reference cycles from my current class to my view controllers? What does NSViewController do to prevent that?

A: NSViewController does absolutely nothing special in regards to memory management, or even for top-level objects.  It just provides a safe place to load a nib and then keep its contents in memory for the duration of the Nib's lifecycle, which means the class itself is little more than an external File's Owner.  Just for kicks, I reimplemented the class, and commented out the interesting bits.  Some stuff, I just outright removed because it was so hacky as to not be worth implementing, or so unused that it didn't warrant re-making.  The full class, with documentation and comments, can be found here;
@interface CFIViewController : NSResponder <NSCoding> {
@private
    NSString *_nibName;
    NSBundle *_nibBundle;
    id _representedObject;
    NSString *_title;
    IBOutlet NSView *view;
    NSArray *_topLevelObjects;
    id _autounbinder; 
    //NSString *_designNibBundleIdentifier;
}

- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle *)nibBundleOrNil;

- (void)setRepresentedObject:(id)representedObject;
- (id)representedObject;

- (void)setTitle:(NSString *)title;
- (NSString *)title;

- (NSView *)view;
- (void)loadView;

- (NSString *)nibName;
- (NSBundle *)nibBundle;

- (void)setView:(NSView *)view;

@end

@implementation CFIViewController

- (void)loadView {
    NSArray *topLevelObjects = nil;

    NSNib *loadedNib = [[[NSNib alloc]initWithNibNamed:self.nibName bundle:self.nibBundle]autorelease];
    if (loadedNib == nil) {
        [NSException raise:NSInternalInconsistencyException format:@"-[%@ %@]", NSStringFromClass(self.class), NSStringFromSelector(_cmd)];
        return;
    }

    BOOL loaded = NO;


#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
    loaded = [loadedNib instantiateWithOwner:self topLevelObjects:&topLevelObjects];
#else 
    loaded = [loadedNib instantiateNibWithOwner:self topLevelObjects:&topLevelObjects];
#endif

    if (loaded) {
        [self _setTopLevelObjects:topLevelObjects];
        [topLevelObjects makeObjectsPerformSelector:@selector(release)];
    } else {
        [NSException raise:NSInternalInconsistencyException format:@"CFIViewController could not instantiate the %@ nib.", self.nibName];
    }

    if (self.view != nil) {
        [self viewDidLoad];
        return;
    }

    [NSException raise:NSInternalInconsistencyException format:@"-[%@ %@]", NSStringFromClass(self.class), NSStringFromSelector(_cmd)];
}

@end

It really is quite a simple mechanism.  All NSViewController really adds to any kind of controller metaphor in Cocoa is the ability to work with NSDocument and it's underlying Core Data mess sanely.  

If the view controller is the nib's "file's owner" won't I instead just be pushing the reference cycles from my current class to my view controllers? What does NSViewController do to prevent that?

NSViewController handles the retention of Top-level objects in one of the most interesting ways I've seen yet.  When it gets a reference to the array containing them, it makes a shallow-copy of the array, then -releases all the old array's objects.  In effect, NSViewController snatches every reference to the NIB's de-frosted objects away from NSCoder, thus guaranteeing a safe release when the array goes away in -dealloc.
However, when it comes to bindings, NSViewController has an internal getter for an NSProxy subclass named NSAutounbinder, which KVO looks for when binding and unbinding objects.  By tweaking release and providing a getter for the internal autounbinder pointer, controller classes can release themselves and their bindings without a fight.  It is absolutely not recommended that you use the implementation in CFIViewController for future OS X releases without verifying that KVO still looks for the autounbinder getter, but for most other releases, it appears to be fine.  CFIViewController provides the option to use the internal NSAutoUnbinder class as of the latest commit, thus resolving any bindings retain cycles.

A: I recommend that you just use NSWindowController or a custom subclass. Your subclass can have a view outlet and a KVO-compliant representedObject property. That should suffice for a 10.4-compatible replacement.
