An introduction to Cocoa Bindings
After learning about Key Value Observing, you might be wondering if there is any way of automatically keep two properties synchronised. Cocoa Bindings, a Mac-only technology, provides this missing link, although it is not as simple as just updating one property whenever the other changes. Bindings are specifically designed to work with views and controllers in AppKit, and contain many features to make them more useful when responding to user interaction.
Most tutorials teach you how to use bindings with Interface Builder. While this is by far the most common way to use bindings, Interface Builder can leave the impression that bindings are a kind of magic, and developers who never dive deeper and gain a full understanding of how they work can often struggle to solve certain problems, especially when trying to debug some unexpected behaviour.
Setting up a binding
Typically, a view is bound to a controller, with a key path that refers to a property on a model object.
Setting up a binding may look like this:
[self.textField bind:@"value" toObject:self.objectController withKeyPath:@"selection.firstName" options:nil];
We can see that there are two objects in play, each with an additional piece of information. The object being bound (usually a view object, in this case an NSTextField) is given a binding name (in this case, “value”). AppKit controllers also expose bindings, so can fulfil this role, but to simplify this introduction, I’ll keep referring to the “view” side of the binding.
The object it is being bound to (usually a controller object, in this case an NSObjectController) is given a key path (selection.firstName) that goes from the controller object to the relevant property on the model object. The controller object and key path together form the “model” side of the binding.
The astute among you will have noticed that when talking about the view, I referred to a binding name rather than a key path. This is another sign that bindings are not symmetrical. On the view side, you bind to a named binding. For any AppKit view or controller, you can find the supported list of bindings in the Cocoa Bindings Reference.
After running the above code, the text field is bound to the model. Whenever the model object’s firstName property changes, or if the model object is switched with another model object inside the object controller, the text field will update itself. If the user makes any changes to the text field, once she has committed editing (by pressing Return, switching focus to another text field, etc), the model’s firstName property will be updated.
Obligations of the model
The model has to support Key Value Observing (KVO), which in turn requires that it supports Key Value Coding (KVC).
There are benefits if the object being bound to implements NSEditorRegistration. This is one reason why it’s a good idea to bind to controller objects rather than binding directly to the model. NSEditorRegistration lets the binding tell the controller that its content is in the process of being edited. The controller keeps track of which views are currently editing the controller’s content. If the user closes the window, for example, every controller associated with that window can tell all such views to immediately commit their pending edits, and thus the user will not lose any data.
Apple supply some generic controller objects (NSObjectController, NSArrayController, NSTreeController) that can be used to wrap your model objects, providing the editor registration functionality. Using a controller also has the advantage that the bindings system isn’t directly observing your model object — so if you replace your model object with a new one (such as in a detail view where the user has changed the record that is being inspected), you can just replace the model object inside the controller, KVO notices and the binding updates.
AppKit’s controller objects can also return placeholder values if they have no model object, or (for NSArrayController and NSTreeController) if multiple objects are selected.
Obligations of the view
In bindings, the view does all the work. It is responsible for observing the model, pushing its own changes back to the model (using Key Value Coding) at an appropriate point, and keeping track of all the information about the binding. If you’re binding to Apple-supplied views (or Apple-supplied controllers that expose bindings) all this is taken care of, but it becomes important if you are creating your own bindable views. Luckily, NSObject has some tricks to help you.
NSObject implements the bind:toObject:withKeyPath:options: method, allowing you to bind any two arbitrary object, but with the following caveats:
- The binding is unidirectional: the model will update the view, but not vice versa.
- NSObject assumes that the binding name is the same as a key path on the view object.
In order to support full bi-directional bindings on a custom view, you have to do some extra work. Apple’s recommended solution is to manually implement bind:toObject:withKeyPath:options:, doing the following:
- Determine which binding is being set.
- Record what object the view is being bound to, using what keypath and with what options. Keep references to all these things, as they’ll be needed later.
- Register as an observer of the keypath of the object to which it is bound so that it receives notification of changes.
When storing references to the model object, make sure to use a weak reference. Before weak references were added to Objective-C, views had to retain their bound model object (in order to avoid a crash trying to access a released object). This led to a high likelihood of retain cycles, and workarounds such as unbinding all the UI before closing the window were common.
Then, in the KVO callback method observeValueForKeyPath:ofObject:change:context:, work out which binding has caused the update (for example, by using the context pointer), and update your view’s internal state based on this new information.
To handle changes going from the view to the model, it’s important to consider when they should take place. It may be more appropriate to send the changes when your view loses focus, rather than sending every change as it happens. (Some of AppKit’s built in bindings have an option to choose when changes are sent. Look at NSContinuouslyUpdatesValueBindingOption on NSTextField’s “value” binding, for example. Your custom view could also allow such an option.)
At the appropriate point, simply use KVC to update the model: use setValue:forKey: using the object and keypath that you stored when the binding was registered. Be aware that your view may immediately receive a KVO update message for a change to the model when you do this: be prepared to handle it gracefully. It is unlikely you’ll get into an infinite loop at this point, since in the absence of a user-initiated action your view won’t be sending information back down the binding, but it’s important to be aware that this may happen.
Supporting NSEditor/NSEditorRegistration will require work on your part as well. Your view will need to implement the NSEditor protocol. In addition, when your view gains focus, it will need to introspect the bound object for each of its bindings, and check whether that object implements the NSEditorRegistration protocol. If it does, then send an objectDidBeginEditing: message to it. When your view loses focus, do the same but with an objectDidEndEditing: message.
Building atop NSObject’s bind:toObject:withKeyPath:options: implementation
When implementing bindings support on a custom view, an alternative approach (see here) is to use NSObject’s default functionality instead of overriding the bind:toObject:withKeyPath:options: method. As mentioned above, this provides a unidirectional binding. But NSObject kindly stores enough information for your view to use to send data in the other direction, and it’s only a little extra work to take advantage of it.
When using this technique, you are still limited to bindings with the same name as a key on your view. This is not typically a large problem, but it can limit the flexibility slightly. Additionally, since the model-to-view direction of the binding is outside your direct control, you are limited to whichever options NSObject supports. Luckily, NSValueTransformerBindingOption (one of the most common options) is supported.
At the point where your view desires to propagate data through the binding to the model, you need to introspect the bindings that are currently set up on your view:
NSDictionary* info = [self infoForBinding:@"value"];
Inside this dictionary, you can use NSObservedObjectKey, NSObservedKeyPathKey and NSOptionsKey to obtain the object that your view is bound to, the key path on that object, and the options that were supplied when creating the binding. Simply call setValue:forKeyPath: on the observed object, using the key path from NSObservedKeyPathKey, and setting the value that your view wishes to propagate.
If you want to support any options when propagating your view’s value to the model, you’ll have to implement the functionality manually. For example, if you want to support NSValueTransformerBindingOption, you’ll need to check if a value transformer was supplied (i.e. whether NSValueTransformerBindingOption is present in the NSOptionsKey dictionary). If so, ask the value transformer for reverseTransformedValue:, and use the result when sending data across the binding using setValue:forKeyPath:.
Supporting NSEditor/NSEditorRegistration will still require manual coding, as described in the previous section.
Unbinding
If you implemented a bindable view from scratch, by overriding bind:toObject:withKeyPath:options:, you should also override unbind:, using this method to unregister from any KVO notifications and to remove the references to the object/keypath/options that you stored.
You should also do this when your view is deallocated.
If you are relying on NSObject’s bind:toObject:withKeyPath:options: implementation, then the framework will take care of unbinding in both of these situations.
Exposing Bindings
When reading the NSKeyValueBindingCreation Protocol Reference, you might come across the exposeBinding: method (and its counterpart, exposedBindings), and assume that you have to expose a binding before you can use it. This is not in fact the case: this method is a legacy from an older version of Interface Builder. Calling this method when running inside an Interface Builder plugin allowed your bindings to show up in Interface Builder’s inspector for your custom view.
The current version of Interface Builder does not support plugins. Xcode 6 is bringing Live Rendering to Interface Builder, which allows developers to annotate certain properties and have them show up in Interface Builder’s inspector, but the support currently does not extend to custom bindings. If you desire such support, please file a duplicate of Radar 2281401.
Conclusion
This article has described the concepts involved in creating bindings in code, and creating both model and view objects that are bindable. Model objects simply require KVO support; view objects require a little more work in order to support all the features of bindings, but (given a firm understanding of KVO/KVC) there is no magic going on: bindings are merely a formalisation of how a few existing technologies can work together to synchronise your app’s model with its view.
Many apps do not require anything more than using the bindings available built in to AppKit controls. These can be very powerful, for example for creating a master/detail interface using table views and an NSArrayController. The NSController objects also support fetching from Core Data, support editing/viewing multiple objects at once, and have a number of other advanced features. To learn more about AppKit’s bindings, look at the Cocoa Bindings Programming Guide (for an overview of how bindings work), and the Cocoa Bindings Reference (for an overview of what bindings are available for AppKit classes).