Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you manually implement Cocoa bindings?

I've had a crack at implementing bindings for my own NSView subclass. It works, but there are problems with retain cycles when binding to File's Owner from a nib file. After reading into it a little, I discovered that Apple had the same problem a few years back but have fixed it with some magic undocumented class (NSAutounbinder).

There is a lengthy discussion of the retain cycle problem here http://www.cocoabuilder.com/archive/message/cocoa/2004/6/12/109600 . The workaround is to to unbind all bindings before the window controller is released, not before it is deallocated, in a place like windowWillClose:. This seems like an unnecessary hack to me.

My question is this: Is there any way to make custom bindings that work as well as the ones made by Apple, without using undocumented features? Am I going about this the wrong way?


UPDATE 2: I have found a solution that allows manually implemented bindings to work exactly like Apple's bindings. It takes advantage of the undocumented NSAutounbinder class, without actually using undocumented features. I will post the solution later today.


UPDATE: I've tried using exposeBinding:, and it doesn't seem to make any difference. However, the NSObject implementation of bind:toObject:withKeyPath:options: half works. It propogates changes from bindee to binder (i.e. from model/controller to view), but doesn't work the opposite way. Also, although the bindee is obviously being observed, observeValueForKeyPath:ofObject:change:context: is never triggered.

Example project here: http://www.tomdalling.com/wp-content/BindingsTest.zip

Apple's documentation indicates that you do, in fact, have to override bind:toObject:withKeyPath:options: to implement manual bindings. See here: http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/HowDoBindingsWork.html


SIDE NOTE: I've investigated how the undocumented NSAutounbinder works, and here's what I know.

When a binding is created to an NSWindowController, the bound object is actually an NSAutounbinder that is acquired from the NSWindowController with -[NSWindowController _autounbinder]. NSAutounbinder is a non-retaining proxy for the NSWindowController object. It is non-retaining to avoid the retain cycle problem.

When -[NSWindowController release] is called and retainCount == 1, The NSAutounbinder unbinds all bindings to itself. This ensures that there are no dangling pointers to the object before it is deallocated.

like image 258
Tom Dalling Avatar asked Jul 23 '09 01:07

Tom Dalling


4 Answers

Here is the best solution I can find. I've got a more detailed discussion and demo code here: http://tomdalling.com/blog/cocoa/implementing-your-own-cocoa-bindings/

Basically, you DO NOT override bind:toObject:withKeyPath:options: or unbind:. The default implementation on NSObject will use NSAutounbinder to avoid retain cycles. As Louis Gerbarg pointed out, there are still situations where NSAutounbinder doesn't kick in. However, you can get your bindings working at least as well as Apple's bindings.

Because the default implementation of bind:toObject:withKeyPath:options: doesn't update the model when the view changes, view-driven changes must be propagated manually. You can use -[NSObject infoForBinding:] to get all the information necessary to update the bound object. I've added my own method on NSObject with a category:

-(void)propagateValue:(id)value forBinding:(NSString*)binding;

It handles getting the bound object, the bound key path, and applying the value transformer. The implementation is available from the link at the top.

like image 171
Tom Dalling Avatar answered Nov 18 '22 07:11

Tom Dalling


The short answer is, no you can't get it to work with no workaround in the calling code and nibs. Even NSAutounbinder misses some cases for the NSDocument and NSWindowController, if Apple can't get it working correctly for 2 classes they specially rig up those of us without access to innards of AppKit have basically no chance.

Having said that, there are two workaround that are maybe a bit nicer than unbinding in windowWillClose:.

  1. Do not bind to File's Owner, but instead to drag an NSObjectController as root level object into the nib and bind to that, then setContents: on the object controller during awakeFromNib.
  2. Turn on Garbage Collection. If that is an option it solves all the object cycle issues ;-) Obviously GC introduces its own issues, and if you need 10.4 compatibility it is a non-starter.
like image 39
Louis Gerbarg Avatar answered Nov 18 '22 07:11

Louis Gerbarg


See mmalc's GraphicsBindings example for a good example of how to implement your own bindings. You need to implement the NSKeyValueBindingCreation informal protocol to get it working. To let your controllers know there are things that can be bound, call exposeBinding in the + (id)initialize method of your view:

+ (void)initialize { [self exposeBinding:@"ILIKEBINDAGE"]; }

You'll then need to implement each of the bindings managing methods in the NSKeyValueBindingCreation protocol. You basically need to setup KVO for the view so that it knows when to update based on the application's behaviors and handle the cleanup (unbind:).

It's a lot of extra, fairly ugly code so it may be that using traditional glue code works better and is easier to read.

like image 3
Justin Williams Avatar answered Nov 18 '22 05:11

Justin Williams


You may want to check out the NSKeyValueBindingCreation Protocol. It lets you create bindings programmatically through code. (Remember to do the work in an awakeFromNib method if you need to reference IBOutlet variables or they could be nil.)

like image 2
Ryan Ballantyne Avatar answered Nov 18 '22 07:11

Ryan Ballantyne