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.
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.
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:.
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.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With