This is fine in most circumstances because we usually bind visuals control to our model. Apple has implemented the two-way logic for us. But what if you wanted to implement this type of ‘magic’ in your own custom views or if you wanted to bind two model objects together? In these cases, you will need to roll out your own bi-directional or two-way bindings. Here I present a simple way to achieve this.
NSObject(BidirectionalBindings)
We want to be able to bind any two type of object. One way to achieve this is to write a category on the NSObject class. In a manner analogue to the NSKeyValueBindingCreation protocol, we need a method for binding and unbinding./* BidirectionalBindings.h */
@interface NSObject(NUBidirectionalBindingProtocol)
- (void) bidirectionalBind: (NSString*) binding
toObject: (id) observable
withKeyPath: (NSString*) keyPath
options: (NSDictionary*) options;
- (void) bidirectionalUnbind: (NSString*) binding;
@end
The Problem
What does the following call mean?[objectA bind: @"foo" toObject:objectB withKeyPath:@"bar" options: nil];The semantics are that objectA should observe objectB for changes in its ‘bar’ field and reflect any changes in that value to it’s own ‘foo’ field. But it does not mean that objectB will observe objectA for changes in ‘foo’. To make thing worse, we can’t ask objectB to observe objectA because we don’t have access to its observeValueForKeyPath:ofObject:change:context: method. What we need is an intermediate object.

Introducing objectX
This objectX, which we control, can observe objectA.foo and progagate the changes back to objectB. This is simple but now we need to keep track of this additional object. Fortunately, the bindings protocol makes it easy for us.The Implementation
What we can do is take advantage of the options dictionary to store custom information. More specifically, the dictionary can store our objectX. I find this to be quite elegant as it avoids having to create other collections to manage these objects and the lifetime of this dictionary is exactly the same as our object. This actually makes our implementation really straight forward. Here is the code to do just that:/* BidirectionalBindings.m */
@interface NUReverseBinding : NSObject {
__weak id forwardToObject;
__weak NSString *forwardToKey;
}
@end
@implementation NUReverseBinding
- (id) initWithBinding: (NSString*) binding toObject: (id) object {
if (self = [super init]) {
forwardToObject = object;
forwardToKey = binding;
}
return self;
}
- (void) observeValueForKeyPath: (NSString*) keyPath
ofObject: (id) object
change: (NSDictionary*) change
context: (void*) context
{
[forwardToObject setValue: [change valueForKey: NSKeyValueChangeNewKey]
forKeyPath: forwardToKey];
}
@end
@implementation NSObject(NUBidirectionalBindingProtocol)
- (void) bidirectionalBind: (NSString*) binding
toObject: (id) observable
withKeyPath: (NSString*) keyPath
options: (NSDictionary*) options
{
NUReverseBinding *reverseBinding = [[NUReverseBinding alloc] initWithBinding: keyPath toObject: observable];
[self addObserver:reverseBinding forKeyPath:binding options:NSKeyValueObservingOptionNew context:nil];
[self bind:binding toObject:observable withKeyPath:keyPath options:
[NSDictionary dictionaryWithObjectsAndKeys:reverseBinding, NUReverseBindingKey, nil]];
[reverseBinding release];
}
- (void) bidirectionalUnbind: (NSString*) binding {
NSDictionary *bindingInfo = [self infoForBinding: binding];
NSDictionary *bindingOptions = [bindingInfo objectForKey:NSOptionsKey];
NUReverseBinding *reverseBinding = [bindingOptions objectForKey:NUReverseBindingKey];
[self removeObserver:reverseBinding forKeyPath:binding];
[self unbind:binding];
}
@end
Demo
Here is a bit of code to demo these bindings:/* main.m */
@interface Foo : NSObject { }
@property (nonatomic, copy) NSString *value;
@end
@implementation Foo
@synthesize value;
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Foo *fooA = [[Foo alloc] init];
Foo *fooB = [[Foo alloc] init];
Foo *fooC = [[Foo alloc] init];
[fooA bidirectionalBind:@"value" toObject:fooB withKeyPath:@"value" options: nil];
[fooB bidirectionalBind:@"value" toObject:fooC withKeyPath:@"value" options: nil];
fooC.value = @"Hello";
NSLog(@"fooA = %@, fooB = %@, fooC = %@", fooA.value, fooB.value, fooC.value);
fooA.value = @"World";
NSLog(@"fooA = %@, fooB = %@, fooC = %@", fooA.value, fooB.value, fooC.value);
[pool drain];
return 0;
}
You will see an output similar to this one on the console:
Running…
2009-09-24 21:28:30.750 fooA = Hello, fooB = Hello, fooC = Hello
2009-09-24 21:28:30.754 fooA = World, fooB = World, fooC = World
Food For Thoughts
One question that is not addressed are binding cycles. Our demo works because there are no cycles of size three or more in the binding chain. If we were to bind fooC to fooA, for example, we would create an infinite loop. I’ve tried this with the standard binding mechanism and it handles cycles well. This crude implementation of bi-directional bindings can be improved in countless ways. But I hope it can still help in a few scenarios of provide the basis for a more complete implementation.Have fun!
