Cocoa for Scientists (Part XIII): In a Bind
Author: Drew McCormack
Website: http://www.maccoremac.com
In the last two tutorials we have covered the underlying technologies that make Cocoa Bindings possible: Key-Value Coding (KVC), and Key-Value Observing (KVO). The former allows you to access attributes using strings to identify them, and the latter allows you to observe when an attribute changes in your code. Today, we are going to show how these technologies are put to use in Cocoa Bindings, to keep the model and view layers in synch.
Cocoa Bindings
Cocoa Bindings, or Bindings for short, is primarily a controller layer technology. In fact, when Apple was first developing it, they even called it the Controller Layer. Basically, it is a set of classes that manage certain types of model objects, and keep the views in synch with the model data. For example, the NSArrayController class manages an array of objects: it can be used to add and remove objects, to sort them, and to select them. You could bind a table view to an NSArrayController to fill it with data. If the user edited the table view, the NSArrayController would automatically propagate the changes to the model layer and update the array of objects represented by the table. And if the array was changed programmatically, the NSArrayController would see this using KVO, and update the table view appropriately.
An important concept in Cocoa Bindings is that of a binding. A binding is a relationship defined by a key path. Bindings are usually defined in view classes; they indicate a relationship between what is displayed in the view, and the data contained in the model objects.
For example, an NSTableColumn, which is the class that represents a single column of data in a table view, has a binding called ‘value’. You bind ‘value’ by assigning it a controller object and a key-path in Interface Builder (or programmatically). Setting the controller to AtomsController, and the key path to atoms.mass, for example, would mean that the table column would show the mass of all the atoms in the atoms array belonging to the AtomsController object. If the atoms array changed, or any atomic mass was modified, the AtomsController would see this and update the table column appropriately.
Bindings can take some time to grok, because most programmers are used to defining relationships between objects using pointers. Usually, one object has a pointer to another and can thereby send it messages. A binding, on the other hand, doesn’t refer to a particular object, but to whatever object or objects happen to be associated with a particular key path at a given time. Think of it like a file system: The key path is like a directory path; in the same way you can say ‘Give me all the files in the Desktop folder, and inform me if anything changes there’, with bindings you can say ‘Give me all the masses of the Atom objects in the atoms array, and inform me if anything changes’. There is no hard-coded pointer between the view and the atoms, just a path through the object graph.
Bindings in Unitary
To make things more concrete, let’s update the Unitary application to use Cocoa Bindings, rather than the older outlet/target/action approach. To follow along, download the Unitary project as we last left it.
First, open the project in Xcode by double clicking the Unitary.xcodeproj file. In the Groups & Files list on the left, open the Classes group, and click on the UnitaryController.h file. The file source should appear in the editor on the right. (You may need to drag the split up from the bottom of the right pane to see the editor.) Replace the code there with the following:
#import <Cocoa/Cocoa.h>
@class Conversion;
@interface UnitaryController : NSObject {
NSArray *conversions;
Conversion *selectedConversion;
double firstValue;
double secondValue;
}
-(NSArray *)conversions;
-(void)setConversions:(NSArray *)newConversions;
-(Conversion *)selectedConversion;
-(void)setSelectedConversion:(Conversion *)newSelectedConversion;
-(double)firstValue;
-(void)setFirstValue:(double)newFirstValue;
-(double)secondValue;
-(void)setSecondValue:(double)newSecondValue;
@end
In many ways, this interface is simpler than the one we had before. It basically consists of a few instance variables, all of which were carried over from the previous version of the class, and accessor methods for each. Note that there are no outlets or actions here, or indeed any references to view objects at all. The previous version of the class had instance variables for each of the text fields in the interface, in order to update them and retrieve user-entered data. Because we are now using bindings, this is not necessary anymore: when the text fields are modified by the user, the accessor methods above will be called automatically.
Bindings in Interface Builder
Although you can bind things together programmatically, usually you will do it in Interface Builder. Open the MainMenu.nib file in the Resources group by double clicking it. Now drag the UnitaryController.h file from Xcode onto the MainMenu.nib window in Interface Builder, and click the Replace button when prompted. This informs IB of the changes we have made in UnitaryController.h.
Now we need to bind the various controls in the Unitary window to the UnitaryController instance:
- Double click the Window instance in the MainMenu.nib window to open it.
- Select the text field next to the Celsius label.
- Bring up the inspector by pressing Command-Shift-I.
- Choose Bindings from the popup button at the top of the inspector.
- Click on the disclosure triangle for the ‘value’ binding.
- In the ‘Bind to’ popup, choose the UnitaryController.
- For the ‘Model Key Path’, enter ‘firstValue’. You should notice the Bind checkbox get checked.
The procedure above is basically the same for setting all bindings in Interface Builder. You select a control, open the Bindings tab in the inspector, choose a binding, set the controller, and set the key path. There are many other options there, but we don’t need to worry about them at this juncture in time.
Repeat this procedure for the text field next to the ‘Fahrenheit’ label, but use the key path ‘secondValue’ instead.

Now we need to bind the labels themselves, so that they change based on the selected conversion. Click on the Celsius label to select it. In the Bindings tab of the inspector, choose the UnitaryController and set the key path to ‘selectedConversion.firstUnitName’. This says that the label will be given by the firstUnitName attribute of whichever Conversion object is selected at the time. Bind the Fahrenheit label to the key path ‘selectedConversion.secondUnitName’.
The only thing left to bind is the popup button used to select the conversion. Binding a popup button is a bit more involved, because you have to set bindings for the list of values the popup contains, as well as for the selection.
- Select the popup button and open the Bindings tab in the inspector.
- Open the ‘content’ binding. Choose the UnitaryController controller, and enter ‘conversions’ for the Model Key Path.
- Close the ‘content’ binding, and open the ‘contentObjects’ binding. Bind that exactly as for the ‘content’ binding.
- Lastly, bind the ‘selectedObject’ binding to the key path ‘selectedConversion’ of the UnitaryController controller.
You are now finished with IB. Save your changes, and return to Xcode.
Implementing the Controller
The final step is to write the implementation of the UnitaryController class. Replace the existing content of the UnitaryController.m file with the following source code:
#import "UnitaryController.h"
#import "Conversion.h"
#import "TemperatureConversion.h"
#import "EnergyConversion.h"
@implementation UnitaryController
-(void)awakeFromNib {
// Create conversion objects
Conversion *temperatureConversion = [[TemperatureConversion alloc] init];
Conversion *energyConversion = [[EnergyConversion alloc] init];
conversions = [[NSArray arrayWithObjects:temperatureConversion, energyConversion, nil] retain];
[temperatureConversion release];
[energyConversion release];
[self setConversions:conversions];
[self setSelectedConversion:[conversions objectAtIndex:0]];
}
-(void)dealloc {
[conversions release];
[selectedConversion release];
[super dealloc];
}
-(NSArray *)conversions {
return conversions;
}
-(void)setConversions:(NSArray *)newConversions {
[newConversions retain];
[conversions release];
conversions = newConversions;
}
-(Conversion *)selectedConversion {
return selectedConversion;
}
-(void)setSelectedConversion:(Conversion *)newSelectedConversion {
[newSelectedConversion retain];
[selectedConversion release];
selectedConversion = newSelectedConversion;
[self setFirstValue:firstValue]; // Sync the values
}
-(double)firstValue {
return firstValue;
}
-(void)setFirstValue:(double)newFirstValue {
firstValue = newFirstValue;
// Update second value
[self willChangeValueForKey:@"secondValue"];
secondValue = [selectedConversion secondValueForFirstValue:firstValue];
[self didChangeValueForKey:@"secondValue"];
}
-(double)secondValue {
return secondValue;
}
-(void)setSecondValue:(double)newSecondValue {
secondValue = newSecondValue;
// Update first value
[self willChangeValueForKey:@"firstValue"];
firstValue = [selectedConversion firstValueForSecondValue:secondValue];
[self didChangeValueForKey:@"firstValue"];
}
@end
This may seem like quite a bit of code, but bear in mind that most of it is made up of accessor methods. The implementation begins with the awakeFromNib method, which sets up and stores the Conversion objects, just as before:
-(void)awakeFromNib {
// Create conversion objects
Conversion *temperatureConversion = [[TemperatureConversion alloc] init];
Conversion *energyConversion = [[EnergyConversion alloc] init];
conversions = [[NSArray arrayWithObjects:temperatureConversion, energyConversion, nil] retain];
[temperatureConversion release];
[energyConversion release];
[self setConversions:conversions];
[self setSelectedConversion:[conversions objectAtIndex:0]];
}
Previously, this method would make a number of calls to view objects, but in the new version of the class, there are no view objects. The awakeFromNib simply initializes the data in the model layer. Through the magic of bindings, this will cause the views to be initialized automatically, because they will observe the changes via KVO.
The accessor methods hold a couple of surprises. The setSelectedConversion: method, for example, looks like this:
-(void)setSelectedConversion:(Conversion *)newSelectedConversion {
[newSelectedConversion retain];
[selectedConversion release];
selectedConversion = newSelectedConversion;
[self setFirstValue:firstValue]; // Sync the values
}
This is all standard, except for the last line. Because the selected conversion is being changed, we need to make sure that the conversion values are updated appropriately. The setFirstValue: call at the end achieves this, because the code of setFirstValue: updates the secondValue attribute to make the model data consistent.
-(void)setFirstValue:(double)newFirstValue {
firstValue = newFirstValue;
// Update second value
[self willChangeValueForKey:@"secondValue"];
secondValue = [selectedConversion secondValueForFirstValue:firstValue];
[self didChangeValueForKey:@"secondValue"];
}
The second half of this method uses the selected conversion to update secondValue so that it is consistent with the new value of firstValue. The setSecondValue is the mirror image of setFirstValue.
One thing that might have you puzzled is the invocations of willChangeValueForKey: and didChangeValueForKey:. These are NSObject methods, and form part of the KVO infrastructure. These methods, when called in this way, allow you to update attributes directly without breaking KVO. Normally, KVO works when an accessor method is called; if you use standard accessors, you get KVO for free. But if you need to update an attribute without calling an accessor, you can still do it, but you have to handle the KVO notifications manually in your code, as shown here. If you didn’t do this, the text field bound to secondValue would not update properly, because the KVO notifications would not fire.
That’s all there is to the new UnitaryController class — pretty straightforward. The beauty of Bindings is that there is no reference in any of this code to any view at all; all relationships between model and view are defined in IB. All we have to do in our controller is supply KVC-conforming accessor methods, and make sure that our data remains in a valid state. Bindings takes care of keeping the model and view in synch.
You can now build and run the application in Xcode. Try entering a few values and change the selected conversion, to see how things update. We have managed to maintain the same behavior as the previous version, and get rid of a lot of code that was used to update and retrieve data from the user interface.
One last thing before concluding: In this example, our controller class, UnitaryController, is a subclass of NSObject. In a real Cocoa application, it would be more common to make UnitaryController a subclass of NSArrayController, which is ready built for managing an arrays of objects. Because this was a simple example, and could be handled without NSArrayController, I decided not to complicate things by including it, but you should at least be aware of this fact. In the weeks and months to come, we will meet NSArrayController many times, and you can learn more about it then.
Conclusions
That’s it for our simple introduction to Bindings. You can download the completed source code here. It can take a while to sink in, but be patient. As we move forward in the series, we will make more and more use of Bindings, and add more complex controls like table views. As you go, bindings will start to make more sense, and eventually become second nature.



Comments
source code
it seems that the completed source code is just the source code from the beginning of this tutorial, i.e. in the .nib-file there are no references to bindings. could you please fix it?
cheers
Source code link fixed
Sorry, the file was there, but the link was wrong. It's fixed now.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Popup labels messed up?
Anybody else have this (this is for the "In a Bind" tutorial):
The popup labels change to
NSKVONotifying_TemperatureConversion: 0x3513e0
EnergyConversion: 0x35920
(each line is surrounded in angle brackets).
Everything works fine but the popups appear to be showing the actual conversion object representations rather than the labels I typed into IB.
I followed the tutorial rather than compiling the downloaded source (it complains about undefined symbols and i686 stuff - I assume Intel Universal stuff... I have a PPC machine).
BTW - excellent series of tutorials!
Thanks,
sa
Popup labels
Yes, I realize now I left out a step. For the popups to work as I bound them, you have to define a description method for each Conversion. This should return a string identifying the object (the default description method returns the string you had above).
So for TemperatureConversion, you could have something like this
-(NSString *)description { return @"Temperature Conversion"; }You can avoid this by binding the contentValues binding of the popup button, but if you do not bind contentValues, it will call the description method to get the labels.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
thanks - setting the
thanks - setting the contentValue binding did it. Question: contentValue looks at the content as set in IB, and doesn't get any the content from the "model" - so this is an either-or?
Thanks,
sa
contentValue binding
I'm not really sure I understand the question. What key path did you bind contentValue to?
Usually you bind it to the titles you want to use in the popup button. For a Conversion object, you could bind to the name of the Conversion, for example.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Model Key Path
I don't quite understand what to use for the model key path for the content values. I have a fair understanding of the other bindings, but the content values throws me.
Thanks for the series, it has helped.
re: contentValues
The contentValues binding is used to bind to the values that you want to appear in the button. So you might have a popup button where each element represents a particular object, but how should they be represented in the popup button?
There are two ways to handle this: add a description method to your objects, that returns the strings that should be used; or, bind contentValues to some path that gives the titles of the objects. Usually this path will be some attribute in your objects, such as 'name' or 'title' or something that means something to the user.
Hope that helps.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Multiple Windows
Fantastic set of tutorials.
I am interested in a program that opens a new GUI window with each user-initiated instantiation of a particular Class. After working through these tutorials, I can't tell how close I am to being able to do that. Is it simple in IB? Will you cover it in future installments?
thanks again,
Adam
Re: Multiple Windows
Hi Adam,
We should meet this sometime in the series, but I can't say exactly when. If you need it now, I suggest taking a look at Apple's docs or a Cocoa book.
If you need a new window each time the user selects a menu item, or something, one way to do it is setup a nib file with the window, and load that each time it is needed. To load a nib, you can use the NSBundle class, and the method loadNibName:owner: in particular.
Something else worth looking at is the NSWindowController class, because it will manage your window for you. If you want to take this route, just create a new NSWindowController each time the user wants a new window, and initialize it with the initWithWindowNibName: method.
Hope that helps.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Yup
That is great help. I have seen docs on the Classes you mention, but also many others. It is very helpful to be aimed in the right direction.
Thanks again,
Adam
When to use Data Bindings
Thanks for the insight!. When would one use data binding instead of explicitly writing code and using pointers to objects by sending messages across like in the previous tutorials?.
Again coming from Visual Studio, I have always dropped components on the surface like a "table" and provided the bindings the rest is automatic. The component would worry about refreshing itself etc and the adapter classes would provide a level is isolation for binding to any datasource.
I do not see third party components that are available to provide various functionalities unlike in the Windows development world there is a plethora of ready built components available.
When to use Cocoa bindings?
This is a good question, and not so easy to answer. I would say that in any new program you are writing, you should try to use Cocoa bindings. Bindings are designed to replace a lot of the old outlet, action/target approach, though it is never possible to fully replace it.
So my advice is to use bindings, unless it is too difficult to do that way for some reason.
As for Windows, I can't really comment much. I'm not sure if the 'bindings' you are referring to are the same in Cocoa as the ones in Windows programming. Bindings in Cocoa is a means of automatically syncing the model and view objects, and is implemented as a mix of controller objects and two Cocoa technologies: Key-Value Coding (KVC) and Key-Value Observing (KVO).
KVO relies on some pretty cool run-time magic. I don't know if other APIs have something similar; it seems to me that it would be difficult to do KVO in a statically typed language like C++, at least in an elegant way.
Can anyone else comment?
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Who owns the controller? I
Who owns the controller?
I gather that the UnitaryController object is created when the nib is loaded, but who owns it? Who will call 'release' on it when the window closes?
I tried to find out by setting a breakpoint in dealloc, but it was never triggered. Perhaps Quit is killing the process rather than exiting through main().
Many thanks for an entertaining set of tutorials.
James