This post is the result of investigation into a stackoverflow.com question of mine.

So, you’ve created a spiffy NSView of your own, and have decided to make it compatible with bindings. Great! So you go and read the documentation, and you look at mmalc’s GraphicsBindings example. You override bind:toObject:withKeyPath:options: and everything works. But wait! Why isn’t the NSWindowController ever being deallocated anymore?

Now you’ve got a nasty retain cycle on your hands. You do a little research and discover that not only do other people have the same problem, but even Apple’s bindings used to have it a few years ago. How did Apple fix the problem? With the magic, undocumented class NSAutounbinder, which nobody seems to know much about.

Other people will tell you that you don’t need to override bind:toObject:withKeyPath:options: and that bindings work automatically. This is only a half truth. NSObject does provide an implementation of bind:toObject:withKeyPath:options:, but it only half works. Using the default NSObject implementation, changes in the model will update the view, but the reverse is not true. When the bound property of the view changes, nothing happens to the model.

So, what is a Cocoa developer to do? I’ll explain how to implement your own bindings that work exactly like Apple’s, with no retain cycles. I haven’t found this solution anywhere else, so as far as I know, I’m the discoverer. I feel so special. It has been mentioned before at least once. The solution is hard to find, though.

The first thing you need to know is that -[NSObject bind:toObject:withKeyPath:options:] will actually use the undocumented NSAutounbinder mechanism to avoid the retain cycle problem. That is half the problem solved right there. So the first step is:

DO NOT override bind:toObject:withKeyPath:options: or unbind:.

Because we’re using the default NSObject implementation, when a bound property changes in the view, we have to manually set the new value on the bound object. This is made possible by the fact that all information about the binding can be obtained from -[NSObject infoForBinding:]. So the second step is:

Use infoForBinding: to propagate view-driven changes

Below is what I use to handle propagation of view-driven changes. It’s a category on NSObject, and is used like so:

-(void)mouseDown:(NSEvent*)theEvent;
{
NSColor* newColor = //mouse down changes the color somehow (view-driven change)
self.color = newColor;
[self propagateValue:newColor forBinding:@"color"];
}

Here is the implementation of propagateValue:forBinding:. It handles value transformers in the binding options.

@implementation NSObject(TDBindings)

-(void) propagateValue:(id)value forBinding:(NSString*)binding;
{
NSParameterAssert(binding != nil);

//WARNING: bindingInfo contains NSNull, so it must be accounted for
NSDictionary* bindingInfo = [self infoForBinding:binding];
if(!bindingInfo)
return; //there is no binding

//apply the value transformer, if one has been set
NSDictionary* bindingOptions = [bindingInfo objectForKey:NSOptionsKey];
if(bindingOptions){
NSValueTransformer* transformer = [bindingOptions valueForKey:NSValueTransformerBindingOption];
if(!transformer || (id)transformer == [NSNull null]){
NSString* transformerName = [bindingOptions valueForKey:NSValueTransformerNameBindingOption];
if(transformerName && (id)transformerName != [NSNull null]){
transformer = [NSValueTransformer valueTransformerForName:transformerName];
}
}

if(transformer && (id)transformer != [NSNull null]){
if([[transformer class] allowsReverseTransformation]){
value = [transformer reverseTransformedValue:value];
} else {
NSLog(@"WARNING: binding \"%@\" has value transformer, but it doesn't allow reverse transformations in %s", binding, __PRETTY_FUNCTION__);
}
}
}

id boundObject = [bindingInfo objectForKey:NSObservedObjectKey];
if(!boundObject || boundObject == [NSNull null]){
NSLog(@"ERROR: NSObservedObjectKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__);
return;
}

NSString* boundKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];
if(!boundKeyPath || (id)boundKeyPath == [NSNull null]){
NSLog(@"ERROR: NSObservedKeyPathKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__);
return;
}

[boundObject setValue:value forKeyPath:boundKeyPath];
}

@end

I hope this helps! I’d like to thank Ryan Ballantyne and Louis Gerbarg for their input, and Peter Hosey for further investigation into the problem.

• http://boredzo.org/ Peter Hosey

I have a couple of minor suggestions:

Use NSParameterAssert instead of that if-then-NSLog block at the top. This way, you get an exception that you can break on.
Use __PRETTY_FUNCTION__ (which is a C string) instead of NSStringFromSelector(_cmd). This way, the class name is included. Not too helpful in this case, since you’re categorying onto NSObject; it’s most useful when you have logging statements in the same method in two or more classes.

With those out of the way: Excellent work. It’s nice to have this Bindings mystery solved. Thank you.

• http://tomdalling.com/ Tom

Thanks Peter. I’ve made those two changes.

I’ve been doing something pretty similar but I hadn’t broken it out into a category. This is a great post and incredibly useful if you’ve been struggling with implementing custom bindings.

• ken

If you have access, you might want to take a look at the Cocoa Tips & Tricks session video from WWDC 2008. That talk covers this technique.

• http://www.bignerdranch.com Aaron Hillegass

Tom, I’m writing a chapter on custom controls in the forthcoming “More Cocoa Programming for Mac OS X”, and I used this code as a starting point for making a control bindable. In the end, my code looks a lot like yours. Is this OK with you?

• http://tomdalling.com/ Tom

That is fine with me. Good luck with the book.

• nonamelive

Hi Tom,

Could you please give me an working example project of your code? I couldn’t make it work since I’m not so sure where to put these codes, especially where to write [self propagateValue:newColor forBinding:@"color"];. Thanks in advance. My email is nonamelive#gmail.com

• http://tomdalling.com/ Tom Dalling

Sorry, I’ve been meaning to get around to this for ages. In the mean time, have a look at mmalc’s examples if you haven’t already:

http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

Hi Tom,

thanks a lot for that piece of code! I used your code to make bindings work in a custom subclass of NSTextField which reacts to scrolling.

• Sergio Moura

Thanks a lot for this. I’ve implemented on a source code project I’m currently working at.

• http://tomdalling.com/ Tom Dalling

Thanks for the credit. I should mention somewhere that the code is totally free to copy with or without credit, but it is provided “as is”, and comes with no warranty, etc.

• Matthew Tebbs

Hi Tom, first thanks for providing this blog article. Very informative. My question is, do you have a standard license for the associated blog source code to be attributed to? I notice that on github.com you’ve used a couple of pre-existing licenses, MIT and Apache v2. Cheers and thanks again!

• http://www.tomdalling.com/ Tom Dalling

I don’t have an explicit license for code snippets on the blog, but let’s just say it’s Apache License V2. I can add the license to the code snippets if you need it, for whatever legal reasons.

• Matthew Tebbs

Thanks for elaborating. Cheers.

• JourH

If your control has any non-instantaneous editing modes, you will also probably want to implement the informal NSEditor protocol, and register your control/view with any Object/Array/Tree controller or the NSDocument it’s bound to, so that your control’s editing behavior can behave like the native bindings-capable controls, and participate in the various lifecycle operations captured by the NSEditor protocol.

• Jorge

Amazing piece of code. I’ve been researching for three days, and this seems to be in the direction of what I’m looking for.
Thanks!