Skip to content

On Cocoa Bindings – “Let Us Break Their Bonds Asunder”

2008 August 27

Chances are, if you’ve developed any kind of Cocoa application, you’ve probably made use of Cocoa Bindings in one way or another, particularly when it comes to Core Data. The technology is ‘key’ to so many of the underlying frameworks but it is a technology that tends to be often-used but frequently not fully understood.

If you are someone who happily binds Array Controllers and View objects together using Interface Builder but have always wondered discretely exactly how the magic behind the scenes happens, or if you just don’t quite comprehend the difference between ‘KVC’ and ‘KVO’ (or perhaps can never quite remember what they stand for…), this article might help you out. In it, I’ll attempt to demonstrate a ‘faux-bindings’ methodology that connects a value from one object with a value from another object, but without using the actual Apple Cocoa Bindings technology. The bare-bones of how the ‘faux binding’ works will be there for all to see; while the internal workings of Cocoa Bindings are a little different, this will hopefully at least show you what, how and why certain things are required for the real Cocoa Bindings to work successfully.

Basic Principles

The simple premise of our ‘faux-bindings’ technology is that we link two objects together such that when a certain value changes in the first, the other changes its linked value to match. One way to accomplish this would be to have the objects (A and B) maintain links to each other such that when object A’s value changes, A can call a method on object B to update B’s value. This is all very well for two simple objects working in one direction but if you have multiple objects all bound together, each with a link to all the others, things can quickly become complicated, leak memory all over the place, and be difficult to maintain.

Notifications

Notifications are another wonderful, often underused, technology that allow communication between objects without those objects knowing anything about each other. Using a Notification Center, objects (e.g. objects B and C) can request notifications when specified types of messages are posted by other objects (e.g. object A). So, when object A decides to post a message, the notification center looks at the list of objects who want to receive such messages, and goes and tells those objects B and C that object A posted a notification. If object A is later released from memory but B and C are still listening for notifications, it doesn’t matter — they maintain no direct link to object A so they simply go on waiting without receiving anything. Similarly, if object C is released before object A posts a notification, again it doesn’t matter — A has no knowledge or worry about who receives the notifications as they go through a third party, a notification center.

To post a notification, an object can simply do this:

	[[NSNotificationCenter defaultCenter] postNotificationName:@"helloIChanged" object:self];

This tells the default notification center (helpfully already created ready for you to use so you don’t need to worry about it) to distribute the ‘helloIChanged’ notification to anyone who listens for it.

To specify that you want to listen for such a notification, you can do this:

	[[NSNotificationCenter defaultCenter] addObserver:self
	                                         selector:@selector(receivedNotification:)
	                                             name:@"helloIChanged"
	                                           object:nil];

This code tells the notification center that you want to receive all ‘helloIChanged’ notifications, and when such a notification is received, your receivedNotification: method should be called. The object parameter of this method allows you to specify that you only want to receive notifications from a single object instance rather than from any object. If you wished, you could say “name:nil” but instead limit your interest to a specific object so that any notification posted by that object (e.g. @”helloIChanged” or @”helloSomethingElseHappened”) would cause the notification centre to call your receivedNotification: method.

The Sample Project

We’re going to create a very simple sample project for this article. If you wish to download some sample code for our Faux Binding, you can do so from here: fauxbindings.zip.

  • In Xcode, create a new ‘Cocoa Application’ project called ‘FauxBindings’.
  • Right-click on the ‘Classes’ group and Add -> New File… to add a basic Objective-C class file called “BindController.m”; make sure the ‘Also create “BindController.h”‘ box is ticked, and click Finish. If you don’t have a two-button mouse, read ‘right-click’ as ‘CTRL-click’ throughout this article.
  • We are going to need an IBOutlet for an NSTextField and a NSString object. We’re also going to need an IBAction method to display the string using NSLog(). So, make your BindController.h file look like this:
    #import <cocoa/Cocoa.h>
     
     
    @interface BindController : NSObject {
     
    	IBOutlet NSTextField *theTextField;
     
    	NSString *theString;
    }
     
    - (IBAction)showTheStringInLog:(id)sender;
     
    @end
  • Next open MainMenu.nib in Xcode and open up the ‘Window’ object. Add an NSTextField and an NSButton called ‘Log theString’ so that it looks something like this:
    The FauxBindings main Window layout in Interface Builder
    Not the most exciting window ever created but functional enough for this demonstration.
  • From the Interface Builder Library tool drag out an NSObject (that’s the blue cube thing) into your MainMenu.nib window. Click this object and in the Inspector, use the Identity tab to specify that the object’s class is BindController. By adding a BindController object in this way to our nib file, an instance of the BindController class will be created when the nib is loaded; there is no other code anywhere in the project that does a [[BindController alloc] init] etc. It’s all handled for you.
  • Next right-click on the cube and connect its theTextField outlet to our text field in the window, and connect the showTheStringInLog: method to the “Log theString” button:
    Connections between the BindController object and the FauxBindings main window
  • Return now to Xcode and open up the BindController.m class file. We need to add the code for the showTheStringInLog: method so write that now:
    #import "BindController.h"
     
     
    @implementation BindController
     
    - (IBAction)showTheStringInLog:(id)sender
    {
    	NSLog(@"Value of theString is: %@", theString);
    }
     
    @end
  • Make sure the Console window is open (listed under the ‘Run’ menu, or CMD-SHIFT-R) and then build and run your application. Press the button. No surprises here, you’ll see that the “Value of theString is: (null)” because we haven’t set theString to anything yet.

Establishing a Link

Now we need to setup a FauxBind: method that can be used to link a specified value of one object to a specified value on another. We’ll use it to link the stringValue on the text field to our BindController’s theString object. We need to call this method when the application first loads so that the faux bindings are ready for the user input.

  • Go back to Interface Builder and click on the Application object in the nib file. Use the Connections Inspector panel to connect the ‘delegate’ outlet to the Bind Controller object. This will allow our object to respond to the ‘awakeFromNib’ method that is called when the application has loaded the nib file into memory.
  • Open BindController.h in Xcode and add two objects to the controller – we’ll need an originatingObject and a destinationObject, together with two strings for that hold the names of the values to which we’ll be binding. Also add a FauxBind: method that will set up our faux binding between those objects. Finally, we’ll be using notifications so declare a method to be called when a notification is received:
    #import <cocoa/Cocoa.h>
     
     
    @interface BindController : NSObject {
     
    	IBOutlet NSTextField *theTextField;
     
    	NSString *theString;
     
    	id originatingObject;
    	NSString *originatingValueName;
    	id destinationObject;
    	NSString *destinationValueName;
    }
     
    - (IBAction)showTheStringInLog:(id)sender;
     
    - (void)FauxBindObject:(id)destObject valueName:(NSString *)destValueName toObject:(id)origObject valueName:(NSString *)origValueName;
     
    - (void)receivedNotification:(NSNotification *)notification;
     
    @end
  • Next open BindController.m and add an awakeFromNib: method after the showTheStringInLog: method. This method says we’re going to bind a value called “theString” in our own object, to a value called “stringValue” on theTextField:
    - (void)awakeFromNib
    {
    	[self FauxBindObject:self valueName:@"theString" toObject:theTextField valueName:@"stringValue"];
    }
  • We can also write the first steps of this faux bind method. Initially, let’s just see what notifications are being posted by theTextField and log them to the console:
    - (void)FauxBindObject:(id)destObject valueName:(NSString *)destValueName toObject:(id)origObject valueName:(NSString *)origValueName
    {
    	[[NSNotificationCenter defaultCenter] addObserver:self
    	                                         selector:@selector(receivedNotification:)
    	                                             name:nil
    	                                           object:origObject];
     
    	originatingObject = origObject;
    	originatingValueName = origValueName;
    	destinationObject = destObject;
    	destinationValueName = destValueName;
    }
  • And, of course, we need to code the receivedNotification: method that is called when a notification is received; here we’ll simply log the whole notification to the console so that we can examine its contents:
    - (void)receivedNotification:(NSNotification *)notification
    {
    	NSLog(@"Received a notification: %@", notification);
    }
  • Again, make sure the console is visible, then build and run the application.

When you click, type and tab out of the text field, you’ll see a whole string of messages sent to the console, indicating a whole chain of notifications. You’ll see an NSControlTextDidBeginEditingNotification at the start, then an NSControlTextDidChangeNotification sent every time the text changed, then (assuming you pressed tab or enter to commit the text field), you’ll see an NSControlTextDidEndEditingNotification.

We want our theString value to change when the text field value is committed by the user pressing tab or enter so:

  • Change the FauxBindObject: method to listen only for NSControlTextDidEndEditingNotifications:
    - (void)FauxBindObject:(id)destObject valueName:(NSString *)destValueName toObject:(id)origObject valueName:(NSString *)origValueName
    {
    	[[NSNotificationCenter defaultCenter] addObserver:self
    	                                         selector:@selector(receivedNotification:)
    	                                             name:NSControlTextDidEndEditingNotification
    	                                           object:origObject];
     
    	originatingObject = origObject;
    	originatingValueName = origValueName;
    	destinationObject = destObject;
    	destinationValueName = destValueName;
    }
  • This time when you build and run, you’ll see that we only receive a notification when the text field commits editing on return or tab.

Having found out when the originating object changes, we need to be able to obtain the value of that object before updating the value of the destination object.

A Note about Selectors

Feel free to skip this part but it might be helpful to know a little about method calls in Objective-C. An in-depth discussion about the way that Objective-C handles such method calls between objects is outside the scope of this article. But, if you are comfortable with bog-standard C or C++, you will know all about functions. Functions look something like this FunctionName( variable1, variable2). When you call a method in Objective-C with code like this: [self doSomethingWith:anObject], the compiler is actually translating this method call into a call to the objc_msgSend() function. This function takes arguments that specify which ‘object’ is receiving the method call, and a ‘selector’ which specifies the name of a method that is being called.

A discussion of how selectors work is again outside the scope of this article but hold in your mind that the messaging function finds out which methods you’re calling (and the calls to those methods) at runtime. This means your code can also refer to methods ‘indirectly’ by using selectors, perhaps in order to find out information about them. We used a selector earlier in our call to register for notifications, saying that our ‘receivedNotification:’ method should be called. There is a method on any NSObject subclass called ‘respondsToSelector:’ which makes it possible to find out whether an object responds to a particular selector (aka method). So, let’s assume for now that the Notification Center will ask our object to make sure it can actually receive the method call we specified. It is then also possible to actually call a method using a selector with the ‘performSelector:’ NSObject method. This functionality is all provided to any object that subclasses NSObject.

Thinking about the Notification Center example, it might do the following when it needs to tell our object that a notification was received:

	if( [object respondsToSelector:@selector(receivedNotification:)] )
	{
		// then actually call the selector
		[object performSelector:@selector(receivedNotification:) withObject:theNotification];
	}

Lastly, you can use a function NSSelectorFromString() to get hold of a selector from a string specifying its name, so the code above could also be written:

	if( [object respondsToSelector:NSSelectorFromString(@"receivedNotification")] )
	{
		// then actually call the selector
		[object performSelector:NSSelectorFromString(@"receivedNotification") withObject:theNotification];
	}

For more information on messaging and selectors, see: Apple Developer Connection: How Messaging Works.

With our short discussion of selectors out of the way, we can now start writing the code to handle accessing and setting our bound values.

Getters and Setters

Our receivedNotification: method is being called now whenever our originating object changes. We can modify that method so that it tries to find out the specified value of the object. To get the value, we need to call a suitable method on the originating object that will return to us that current value. The most sensible name for such a method would be a method with the same name as the name of the value — in other words, if an object has a ‘someValue’ internal value, you could call:

	id theValue = [object someValue];

to find out that value.

On this assumption then, our method needs to find out whether our originating object responds to a method with the name of the originating value name and, if so, call that method to acquire the current value.

  • Open BindController.m in Xcode and modify your receivedNotification: method to look like the following:
    - (void)receivedNotification:(NSNotification *)notification
    {
    	NSLog(@"Received a notification that an object changed");
     
    	if( [notification object] == originatingObject )
    	{
    		NSLog(@"Originating Value to fetch is: %@", originatingValueName);
     
    		// see if there is a method to fetch this value name
    		NSString *getterMethodName = originatingValueName;
    		id fetchedValue = nil;
    		NSLog(@"Originating Method to test is: %@", getterMethodName);
     
    		if( [originatingObject respondsToSelector:NSSelectorFromString(getterMethodName)] )
    		{
    			NSLog(@"Originating Object responds to: %@", getterMethodName);
    			fetchedValue = [originatingObject performSelector:NSSelectorFromString(getterMethodName)];
    			NSLog(@"Fetched value is: %@", fetchedValue);
    		}
    		else
    		{
    			NSLog(@"Originating Object is not KVC compliant for the value: %@", originatingValueName);
    			return;
    		}
    	}
    }

If you examine this code, you’ll see that we are first of all checking to make sure that the notification we are receiving has come from the object in which we are interested. We then look at the string containing the name of the value we want, and ask our originating object whether it responds to a method with the name of that value. Assuming it does, we fetch the value by calling that method.

Build and run the project and type something in the text field before pressing tab or enter. You’ll see in the console log all the various steps above and if everything goes to plan, you should see something like:

2008-08-27 14:33:26.756 FauxBindings[2411:10b] Received a notification that an object changed
2008-08-27 14:33:26.761 FauxBindings[2411:10b] Originating Value to fetch is: stringValue
2008-08-27 14:33:26.785 FauxBindings[2411:10b] Originating Method to test is: stringValue
2008-08-27 14:33:26.805 FauxBindings[2411:10b] Originating Object responds to: stringValue
2008-08-27 14:33:26.823 FauxBindings[2411:10b] Fetched value is: something

KVC Compliance

You’ll notice in the above code that if an object doesn’t respond to our suspected getter method name, we log a message to the console that the object isn’t KVC compliant for that value. To test this, change the call in the awakeFromNib: method to something like this:

- (void)awakeFromNib
{
	[self FauxBindObject:self valueName:@"theString" toObject:theTextField valueName:@"anUnknownValue"];
}

When you run the application and modify the text field, you’ll see that the object doesn’t respond to a method called ‘anUnknownValue‘ and so we can’t acquire that value.

KVC stands for Key-Value-Coding; what this means is that an object provides suitably named methods to access its values. In the above example, we ask our theTextField for the value named “anUnknownValue”. This ‘value name’ is also referred to as a ‘key’ so it would be best to say:

“We ask our theTextField object for the value of its “anUnknownValue” key”

For an object to be KVC-compliant for a particular key, it must provide methods to access the value of the key that can be worked out given the name of that key. This generally means that a ‘getter’ method is given the same name as the name of the key (as in “stringValue”). There are other possibilities too; it might make sense for a Boolean (true/false) value called “onWatchList” to be accessed using an accessor “isOnWatchList” and in full-blown Cocoa bindings, there are various options you can use when naming accessor methods. For the purposes of this article, however, we won’t include checking for those other options in our FauxBind: method.

The other half of KVC-compliance is that an object provide a suitably-named ‘setter’ method, i.e. a method to set the value of its key. Although there are again various options, this generally means that you put the word ‘set’ at the front of the name of the key and correct the capitalization such that for a value “stringValue”, the setter would be “setStringValue”.

With this knowledge, we can go about changing the receivedNotifiction: method to try and set the value of the key on the destination object using a KVC setter method.

  • If you changed the “stringValue” key above to “anUnknownValue” etc then change it back now:
    	[self FauxBindObject:self valueName:@"theString" toObject:theTextField valueName:@"stringValue"];
  • Modify the receivedNotification: method to the following:
    - (void)receivedNotification:(NSNotification *)notification
    {
    	NSLog(@"Received a notification that an object changed");
     
    	if( [notification object] == originatingObject )
    	{
    		NSLog(@"Key to fetch is: %@", originatingValueName);
     
    		// see if there is a method to fetch this value name
    		NSString *getterMethodName = originatingValueName;
    		id fetchedValue = nil;
    		NSLog(@"Originating Method to test is: %@", getterMethodName);
     
    		if( [originatingObject respondsToSelector:NSSelectorFromString(getterMethodName)] )
    		{
    			NSLog(@"Originating Object responds to: %@", getterMethodName);
    			fetchedValue = [originatingObject performSelector:NSSelectorFromString(getterMethodName)];
    			NSLog(@"Fetched value for key is: %@", fetchedValue);
    		}
    		else
    		{
    			NSLog(@"Originating Object is not KVC compliant for the key: %@", originatingValueName);
    			return;
    		}
     
     
    		NSLog(@"\n");
     
    		NSLog(@"Destination's Key to change is: %@", destinationValueName);
     
    		// see if there is a method to change this value name
    		NSString *setterMethodNameFirstCharacter = [destinationValueName substringToIndex:1];
    		NSString *setterMethodName = [NSString stringWithFormat:@"set%@%@:", [setterMethodNameFirstCharacter capitalizedString], [destinationValueName substringFromIndex:1]];
     
    		NSLog(@"Destination Method to test is: %@", setterMethodName);
     
    		if( [destinationObject respondsToSelector:NSSelectorFromString(setterMethodName)] )
    		{
    			NSLog(@"Destination Object responds to: %@", setterMethodName);
     
    			[destinationObject performSelector:NSSelectorFromString(setterMethodName) withObject:fetchedValue];
    		}
    		else
    		{
    			NSLog(@"Destination Object is not KVC complicant for the value: %@", destinationValueName);
    		}
    	}
    }

The additional code above now uses various NSString methods to get a suitable setter method name string by putting the word “set” on the front of the key name, which in turn has had its first letter capitalized.

If you run the application now, you’ll see the following:

2008-08-27 15:09:30.464 FauxBindings[2587:10b] Received a notification that an object changed
2008-08-27 15:09:30.467 FauxBindings[2587:10b] Key to fetch is: stringValue
2008-08-27 15:09:30.482 FauxBindings[2587:10b] Originating Method to test is: stringValue
2008-08-27 15:09:30.494 FauxBindings[2587:10b] Originating Object responds to: stringValue
2008-08-27 15:09:30.498 FauxBindings[2587:10b] Fetched value for key is: something
2008-08-27 15:09:30.499 FauxBindings[2587:10b]
2008-08-27 15:09:30.502 FauxBindings[2587:10b] Destination's Key to change is: theString
2008-08-27 15:09:30.503 FauxBindings[2587:10b] Destination Method to test is: setTheString:
2008-08-27 15:09:30.504 FauxBindings[2587:10b] Destination Object is not KVC complicant for the value: theString

The method is trying to set the value for the ‘theString’ key but fails because our BindController object isn’t KVC compliant for the ‘theString’ key — which is because we haven’t implemented a ‘setTheString’ method in our BindController object. Let’s do that now:

  • In BindController.h, add a method declaration for the ‘setTheString’ method:
    #import <cocoa/Cocoa.h>
     
     
    @interface BindController : NSObject {
     
    	IBOutlet NSTextField *theTextField;
     
    	NSString *theString;
     
    	id originatingObject;
    	NSString *originatingValueName;
    	id destinationObject;
    	NSString *destinationValueName;
    }
     
    - (IBAction)showTheStringInLog:(id)sender;
     
    - (void)FauxBindObject:(id)destObject valueName:(NSString *)destValueName toObject:(id)origObject valueName:(NSString *)origValueName;
     
    - (void)receivedNotification:(NSNotification *)notification;
     
    - (void)setTheString:(NSString *)value;
     
    @end
  • Implement this method in BindController.m with the following code:
    - (void)setTheString:(NSString *)value
    {
    	NSLog(@"Setting theString to value: %@", value);
    	theString = value;
    }

Now when you run the application, press the ‘Log theString’ button before doing anything else, then type something in the text box and press enter, then press the ‘Log theString’ button again. If all has gone to plan, you’ll have seen the following in the Console:

2008-08-27 15:20:41.207 FauxBindings[2652:10b] Value of theString is: (null)
2008-08-27 15:20:44.022 FauxBindings[2652:10b] Received a notification that an object changed
2008-08-27 15:20:44.023 FauxBindings[2652:10b] Key to fetch is: stringValue
2008-08-27 15:20:44.034 FauxBindings[2652:10b] Originating Method to test is: stringValue
2008-08-27 15:20:44.040 FauxBindings[2652:10b] Originating Object responds to: stringValue
2008-08-27 15:20:44.045 FauxBindings[2652:10b] Fetched value for key is: something
2008-08-27 15:20:44.064 FauxBindings[2652:10b]
2008-08-27 15:20:44.070 FauxBindings[2652:10b] Destination's Key to change is: theString
2008-08-27 15:20:44.074 FauxBindings[2652:10b] Destination Method to test is: setTheString:
2008-08-27 15:20:44.085 FauxBindings[2652:10b] Destination Object responds to: setTheString:
2008-08-27 15:20:44.086 FauxBindings[2652:10b] Setting theString to value: something
2008-08-27 15:20:45.222 FauxBindings[2652:10b] Value of theString is: something

Finally, we have a working binding between our text field’s stringValue key and our BindController object’s theString key. When the text field posts the notification that its value has changed, the value of our theString key is updated to match the new stringValue value.

Reversing the Process

Let’s see if we can get this working the other way around. In other words, can we get our theTextField object to update its stringValue when our BindController object’s theString is changed?

  • First of all, change the awakeFromNib: method so that the binding happens the other way around:
    - (void)awakeFromNib
    {
    	[self FauxBindObject:theTextField valueName:@"stringValue" toObject:self valueName:@"theString"];
    }
  • Next, we’ll change the user interface so that we can specify a value for theString and see what happens. Open up BindController.h and add another IBOutlet for an NSTextField. Also add a method to be called to set theString to the value of the text field:
    #import <cocoa/Cocoa.h>
     
     
    @interface BindController : NSObject {
     
    	IBOutlet NSTextField *theTextField;
    	IBOutlet NSTextField *secondTextField;
     
    	NSString *theString;
     
    	id originatingObject;
    	NSString *originatingValueName;
    	id destinationObject;
    	NSString *destinationValueName;
    }
     
    - (IBAction)showTheStringInLog:(id)sender;
     
    - (void)FauxBindObject:(id)destObject valueName:(NSString *)destValueName toObject:(id)origObject valueName:(NSString *)origValueName;
     
    - (void)receivedNotification:(NSNotification *)notification;
     
    - (void)setTheString:(NSString *)value;
     
    - (IBAction)modifyTheStringToValueOfTextField:(id)sender;
     
    @end
  • Open MainMenu.nib in Interface Builder and add a second text field along with a second button, and connect these to the new outlet and action. My interface looks like this:
    The revised window for Faux Bindings in Interface Builder
  • Return to Xcode and open BindController.m, adding in the implementation for the modifyTheStringToValueOfTextField: method:
    - (IBAction)modifyTheStringToValueOfTextField:(id)sender
    {
    	[self setTheString:[secondTextField stringValue]];
    }

Notice that we set the value of ‘theString’ using the KVC setter method and not directly. Why we do this will become clear in a minute…

If at this point you build and run the application, you’ll find that nothing happens at all when you enter text in the second field and click the button other than a console log that theString was set to a new value. Why doesn’t the supposedly-bound text field update to match the new value of theString? Because no notification is ever sent that its value changed.

  • Change the setTheString: method to the following:
    - (void)setTheString:(NSString *)value
    {
    	NSLog(@"Setting theString to value: %@", value);
    	theString = value;
    	[[NSNotificationCenter defaultCenter] postNotificationName:NSControlTextDidEndEditingNotification object:self];
    }

Now, when you type something in the second text field (and press enter to commit it), then click the button, our notification method tries to obtain the new value of theString but complains that our BindController object is not KVC compliant for theString:

2008-08-27 15:48:19.487 FauxBindings[2869:10b] Setting theString to value: something
2008-08-27 15:48:19.494 FauxBindings[2869:10b] Received a notification that an object changed
2008-08-27 15:48:19.514 FauxBindings[2869:10b] Key to fetch is: theString
2008-08-27 15:48:19.521 FauxBindings[2869:10b] Originating Method to test is: theString
2008-08-27 15:48:19.523 FauxBindings[2869:10b] Originating Object is not KVC compliant for the key: theString

Why? Because there is no getter method defined in the BindController object.

  • Open BindController.h and add a declaration for the getter method underneath its setter declaration:
    - (NSString *)theString;
  • Implement this method in BindController.m thus:
    - (NSString *)theString
    {
    	return theString;
    }

This time when you run the application, you should find that everything functions as expected. Type something in the second field, press enter and then press the button. The first text field updates its content to match.

This is where the KVO comes from — standing for Key-Value-Observing. For an object to allow Observation of its Key Values, its setter methods must announce when they are changing a particular value so that notifications can be picked up and changes propagated.

One more Check

Just one final way to demonstrate that this functionality works is to modify the awakeFromNib: method to connect the two text fields directly.

  • Modify awakeFromNib: to this:
    - (void)awakeFromNib
    {
    	[self FauxBindObject:secondTextField valueName:@"stringValue" toObject:theTextField valueName:@"stringValue"];
    }

This time, if you type something in the first text field and press enter, the second text field will change its value; note that there is no BindController theString value changing here, it’s only the two text fields. If you want the first text field to update when the second changes too, you would have to initiate that binding separately.

Starting to Replace Code

Assuming that an object is KVC-compliant (remember, that’s when it has suitably named accessors), you can access a named value (key) from that object by using the method valueForKey: and set it using setValue:forKey:. These methods perform all the necessary magic to find out if an object has KVC accessors and, assuming they do, call them to access the values.

  • We can simplify our receivedNotification: method somewhat by changing it to the following:
    - (void)receivedNotification:(NSNotification *)notification
    {
    	NSLog(@"Received a notification that an object changed");
     
    	if( [notification object] == originatingObject )
    	{
    		NSLog(@"Key to fetch is: %@", originatingValueName);
     
    		id fetchedValue = [originatingObject valueForKey:originatingValueName];
     
    		NSLog(@"Destination's Key to change is: %@", destinationValueName);
     
    		[destinationObject setValue:fetchedValue forKey:destinationValueName];
    	}
    }

If you run using this code, you’ll see that everything happens as before, only we’re using the valueForKey: and setValue:forKey: methods instead of our previous logic.

Proper KVO

You’ll remember that earlier we ‘faked’ an NSControlTextDidEndEditing notification to force our demonstration to work when our theString key was modified — whilst not being problematic in this particular demonstration, it’s not good practice to post notifications that aren’t legitimate. It would be better in this case to post a “theStringValueDidChange” notification that is more suitably named (and, if you did this, you’d obviously also need to change the notification registration code to specify the new notification type that the object was hoping to receive).

For information on naming conventions for notifications (and other things), see: Apple Developer Connection : Naming Instance Variables and Data Types.

In a real bindings scenario, you use two methods willChangeValueForKey: and didChangeValueForKey: to tell the bindings mechanism that suitable notifications should be sent out. Our setTheString: method should really look like this:

- (void)setTheString:(NSString *)value
{
	[self willChangeValueForKey:@"theString"];
	NSLog(@"Setting theString to value: %@", value);
	theString = value;
	[self didChangeValueForKey:@"theString"];
}

Again, these methods are available to any subclass of NSObject.

When writing Core Data NSManagedObject subclasses, you’ll find that you also need to use willAccessValueForKey: and didAccessValueForKey: in your getter accessor methods — these inform the managed object that it needs to fetch the relevant data from the store if necessary.

Real Bindings

Now that we know what’s going on behind the scenes, let’s change our sample application to use real bindings rather than our faux bindings.

  • First off, our BindController object now doesn’t need to do anything about notifications so we can remove the methods that handle them. We’ll also take out the second text field and button methods. Change the BindController.h file to the following:
    #import <cocoa/Cocoa.h>
     
     
    @interface BindController : NSObject {
     
    	IBOutlet NSTextField *theTextField;
     
    	NSString *theString;
    }
     
    - (IBAction)showTheStringInLog:(id)sender;
     
    - (void)setTheString:(NSString *)value;
    - (NSString *)theString;
     
    @end
  • In Interface Builder, remove the second text field and button from the Window.
  • We also need to remove the unused methods from BindController.m and modify our awakeFromNib: method to use proper Cocoa Bindings. Change the file to the following:
    #import "BindController.h"
     
     
    @implementation BindController
     
    - (IBAction)showTheStringInLog:(id)sender
    {
    	NSLog(@"Value of theString is: %@", theString);
    }
     
    - (void)awakeFromNib
    {
    	[theTextField bind:@"value" toObject:self withKeyPath:@"theString" options:nil];
    }
     
    - (void)setTheString:(NSString *)value
    {
    	[self willChangeValueForKey:@"theString"];
    	NSLog(@"Setting theString to value: %@", value);
    	theString = value;
    	[self didChangeValueForKey:@"theString"];
    }
     
    - (NSString *)theString
    {
    	return theString;
    }
     
    @end

In the awakeFromNib: method, we bind the text field’s value to our theString key. All the magic is handled for us and the application behaves as expected — typing text in the text field and pressing enter will update theString.

To be able to do this the other way around using Cocoa Bindings (i.e. change the value of the text field when theString changes), you need to make a further modification.

  • Change the BindController.m file to the following:
    #import "BindController.h"
     
     
    @implementation BindController
     
    + (void)initialize
    {
    	[self exposeBinding:@"theString"];
    }
     
    - (IBAction)showTheStringInLog:(id)sender
    {
    	NSLog(@"Value of theString is: %@", theString);
    }
     
    - (void)awakeFromNib
    {
    	[self bind:@"theString" toObject:theTextField withKeyPath:@"stringValue" options:nil];
    }
     
    - (void)setTheString:(NSString *)value
    {
    	[self willChangeValueForKey:@"theString"];
    	NSLog(@"Setting theString to value: %@", value);
    	theString = value;
    	[self didChangeValueForKey:@"theString"];
    }
     
    - (NSString *)theString
    {
    	return theString;
    }
     
    @end

You’ll notice that there is also now an ‘initialize’ class method. If you’re not sure what this means, a class method is one that is called on a class rather than an instance of that class and is designated as such with a ‘+’ in front of the method declaration rather than a ‘-‘. The + (void)initialize method can be called like this:

	[BindController initialize];

whereas - (void)awakeFromNib, for example, must be called on an instance of the object like this:

	BindController *bcInstance = [[BindController alloc] init];
	[bcInstance awakeFromNib];

(and yes, there’s a memory leak just in that little code snippet — “if you’re alloc-initing, you should be releasing” [or “auto-releasing”…])

The +initialize method is called before any instances of our object are used in code so that features of the class are known for the future; here we’re exposing the fact that our BindController object has a bindable key called ‘theString’.

In the awakeFromNib: method, we then actually bind this bindable key to the stringValue key on our text field.

If you build and run the application with the binding set up this way round, obviously you’ll find that changing the value of the text field doesn’t have any effect on our theString key. But, remember earlier in this article that when our theString attribute hadn’t yet been initialized to any value, the ‘Log theString’ button would cause a ‘Value of theString is: (null)’ to be displayed. This time, however, if you click the button, you’ll find that it outputs ‘Value of theString is: ‘ with a blank string (@”” in object terms) as the value (and indeed, outputs this to the console when you first run the application). You might like to philosophize to yourself about where that comes from…

Things to Note

There are a few further points worth noting from all this:

  • You could bind to the BindController ‘theString’ key from another object rather than in the BindController’s awakeFromNib: method since its binding is ‘exposed’. For example:
    	NSTextField *someTextField = //get a text field from somewhere
    	BindController *bcInstance = [[BindController alloc] init];
     
    	[someTextField bind:@"stringValue" toObject:bcInstance withKeyPath:@"theString" options:nil];
  • The name of the attribute that holds a value does not need to be the same as the key for that value. It is common practice to follow a different naming convention for attributes that are in the interface for the class rather than attributes allocated inside methods, one example being to put an underscore character on the front of class attributes. Storage of the value for our theString key could be done like this:
    - (void)setTheString:(NSString *)value
    {
    	[self willChangeValueForKey:@"theString"];
    	NSLog(@"Setting theString to value: %@", value);
    	_theString = value;
    	[self didChangeValueForKey:@"theString"];
    }
     
    - (NSString *)theString
    {
    	return _theString;
    }
  • There are various other features in KVC and KVO that I haven’t discussed here such as if one key’s value depends on another key’s value, you can specify using another class method of the form keyPathsForValuesAffecting... such that if that depended-upon value changes, the Bindings mechanism will automatically notify objects bound to your dependent key of the change. Consider the following example where there is a second string key value exposed to which objects can bind called “dependentString”. It simply returns a modified version of theString so if an object is bound to that and theString changes, they need to be told so that they can request the new version of dependentString, all of which is accomplished quite simply, like this:
    - (void)setTheString:(NSString *)value
    {
    	[self willChangeValueForKey:@"theString"];
    	NSLog(@"Setting theString to value: %@", value);
    	_theString = value;
    	[self didChangeValueForKey:@"theString"];
    }
     
    - (NSString *)theString
    {
    	return _theString;
    }
     
    - (NSString *)dependentString
    {
    	return [[self theString] stringByAppendingString:@" Plus Something Else"];
    }
    // note dependentString has no setter
     
    + (NSSet *)keyPathsForValuesAffectingDependentString
    {
    	return [NSSet setWithObject:@"theString"];
    }
  • There are also various validation methods that, if they exist, can be called automatically to make sure that a value is suitable before allowing it to be set for a particular key. See Apple Developer Connection : KVC Key-Value Validation for more details but here is a small example:
    - (BOOL)validateTheString:(id *)valueRef error:(NSError **)outError
    {
        // Insert custom validation logic here.
        return YES; // YES if is valid, NO otherwise
    }
  • It’s worth noting that if an object doesn’t appear to be KVC-compliant for a particular value, the system will call the ‘valueForUndefinedKey:’ method on an object which, on NSObject, raises an exception (and thus halts program execution). You can override this method, if you wish, to prevent exceptions being raised and to provide some sort of ‘default’ value if such a thing is meaningful.
  • There is also a very useful ‘dictionaryWithValuesForKeys:’ method which will build an NSDictionary containing the specified keys and their respective values — useful if you need to access, persist or copy several values and don’t want to call ‘valueForKey:’ etc on each one. The equally useful setter equivalent of this is ‘setValuesForKeysWithDictionary:’ which does exactly what it sounds like.

The End

I hope that this article has been of some use and/or interest if you’ve made it this far. Obviously I haven’t talked here about KVC compliance for accessing arrays, sets and scalar values etc but these aren’t at all complicated, provided the fundamental ideas are understood. It is also worth thinking about whether accessor methods should retain and release their values under traditional memory management.

Finally, since most accessor methods follow standard formats (i.e. broadcast that you’re changing a value, change the value, broadcast that you’ve changed the value) it’s possible to automate the process of accessor creation, most notably using Objective-C 2.0 properties. Using @property and @synthesize are both powerful features of the language for all sorts of reasons although many developers remain unconvinced of their usefulness. Using Objective-C properties offers faster (in terms of processing cycles) access to an object’s keys than either using traditional accessors, or (even slower) using valueForKey: etc. But, because of the need for terms used to specify how a @property is @synthesized, it is easy to pick the wrong words (e.g. atomic, retain, copy, readwrite etc) and not be able to work out why a particular KVC procedure or Bindings technique is crashing. Use of Objective-C 2.0 code also means your application won’t run on anything earlier than Mac OS X 10.5 Leopard.

Should you be interested in reading more about views on Objective-C 2.0 properties, take a look at: Matt Gallagher — In defense of Objective-C 2.0 Properties.

This post contains links to various other posts that attack properties, and also clarifies their purpose, notably, “not to provide auto-generated getter and setter methods” (although this is certainly one side effect of their use!).

As always, all comments are welcomed either as Blog post comments below or by email.

Happy Binding!

Share
4 Responses leave one →
  1. Steve permalink
    August 27, 2008

    The code:
    [self willChangeValueForKey:@”theString”];

    is not needed in the setter for a value. It is automatically generated.

  2. August 28, 2008

    This is indeed so — and thank you for pointing this out; I should have included information on this in the body of the post.

    By default, NSObject provides automatic key-value observing. Under this default behaviour, you in fact do not actually need to call either ‘willChangeValueForKey:’ or ‘didChangeValueForKey:’ as these will be called for you if a call is made to a KVC accessor method (e.g. ‘setTheString:’).

    But, you can also disable this automatic notification such that you do need to notify observers manually; this is extremely useful in cases where you need to change large numbers of values and want to control when notifications are sent out. It’s also useful in that you can change the behaviour to send notifications only if the new value is different from its current value:

    - (void)setTheString:(NSString *)value
    {
    	if (  ![_theString isEqualToString:value] )
    	{
    		[self willChangeValueForKey:@"theString"];
            	_theString = value;
            	[self didChangeValueForKey:@"theString"];
    	}
    }

    You can override the class method +automaticallyNotifiesObserversForKey: to specify that certain keys do not provide the automatic behaviour:

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
    {
    	BOOL automatic = NO;
     
    	if ( [theKey isEqualToString:@"theString"] )
    	{
            	automatic=NO;
        	}
    	else
    	{
    		automatic=[super automaticallyNotifiesObserversForKey:theKey];
    	}
    	return automatic;
    }

    Both these methods and further explanation can be found at: Apple Developer Connection : KVO Programming Guide : Automatic Versus Manual Support.

  3. twobyte permalink
    September 4, 2008

    You can check some more bindings examples from Malcolm Crawford who is BTW a Senior Technical Writer at Apple Inc. here http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

Trackbacks and Pingbacks

  1. craschworks » Blog Archive » links for 2009-07-06

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS