views:

224

answers:

3

I'm trying to implement my own version of NSViewController (for backwards compatibility), and I've hit a problem with bindings: Since bindings retain their target, I have a retain circle whenever I bind through File's owner.

So I thought I'd just explicitly remove my view from its superview and release the top level objects, and that would take care of the bindings, because my controller isn't holding on to the views anymore, so they release me and I can go away. But for some reason, my view controller still doesn't get released. Here's a sample app exhibiting the problem:

http://dl.dropbox.com/u/34351/BindingsLeak.zip

Build it, launch it, and hit Cmd-K ("Create Nib" in "Edit" menu) to load a NIB into the empty window. Hit Cmd-K again to release the first view controller (TestNibOwner) and load a new one. The old view controller never gets dealloced, though.

Remove the "value" binding on the checkbox, and it gets released just fine.

If you set breakpoints at the release/retain/autorelease overrides, you see that _NSBindingInfo retains the TestNibOwner, but never releases it in the leaking case.

Anyone know how to fix this?

A: 

When you remove your view from its superview, are you also sending it another -release message? It was created by unarchiving from the nib, right?

NSResponder
Yes, releaseTopLevelObjects releases the view (and any other unarchived objects that may be there). At least that's what it's supposed to do.
uliwitness
+2  A: 

Doing a little investigation with class-dump and friends, it looks like Apple has a private class called NSAutounbinder that takes care of this dirty work for classes such as NSViewController and NSWindowController. Can't really tell how it works or how to replicate it though.

So, I can't really answer your question on how to prevent the retain cycle from happening for arbitrary bindings in a loaded nib, but perhaps it's some consolation to know that Apple is cheating, and you're not missing anything obvious. :-)

Brian Webster
+1  A: 

One thing I've done for the same problem is to create a proxy NSObjectController inside my nib. My NSViewController-like class has a pointer to this proxy and all bindings are bound through it. When I want to cleanup the view controller, I then do [selfProxy setContent:nil] on the object controller and release the view controller. In this instance the NSObjectController proxy acts as the auto-unbinder in this case.

It's more manual and you can't just release the view by itself, but it does solve the retain problem.

I'd suggest you do this:

-(void) releaseTopLevelObjects
{
    // Unbind the object controller's content by setting it to nil.
    [selfProxy setContent:nil];

    NSLog( @"topLevelObjects = %@", topLevelObjects );
    [topLevelObjects release];
    topLevelObjects = nil;
}

In your nib, bindings would happen through a path like:

selfProxy.content.representedObject.fooValue
Mike Shields
Thanks! I've seen many hackish attempts at fixes on the web, but this is a workaround I would actually dare to ship. Too bad there's no *real* way to do the auto-unbinding, though.
uliwitness