Objective-C KeyPath Bindings

Light through a keyholeOne of the nicer features surrounding Objective-C are “Key Value Observation”:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/ (KVO) and “Key Value Coding”:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/KeyValueCoding.html. KVO provides a mechanism to observe changes in a property on an object. Key Value Coding is an informal protocol — which NSObject implements — that gives you the ability to query an object and its properties.

There are times when an object wants to observe changes in a property of another object and mirror those changes in a property of its own. Unfortunately, implementing this behavior requires a verbose set of KVO code that has to be duplicated between objects that want to have this behavior.

*For example:*


static void *kBindingContext = &kBindingContext;

@implementation MySlider
@synthesize requestModel;
@synthesize percent;

- (id)init {
  if((self = [super init])) {
    [requestModel addObserver:self forKeyPath:@"percentUploaded" options:0 context:kBindingContext];   
  }
  return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if (context == kBindingContext) {
    self.percent = self.requestModel.percentUploaded;
  } else {
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  }
}

- (void)dealloc {
  [requestModel removeObserver:self forKeyPath:@"percentUploaded"];
  [requestModel release];
  [super dealloc];
}
@end

Having to do this _on more than one occasion_ led me to create a library called “Key Path Bindings”:https://github.com/dewind/KeyPathBindings that simplifies and reduces the complexity of binding properties to a key path on another object.

*With Key Path Bindings:*

@implementation MySlider
@synthesize requestModel;
@synthesize percent;

- (id)init {
  if((self = [super init])) {
    [requestModel bindProperty:@"percent" onTarget:self toKeyPath:@"percentUploaded"];   
  }
  return self;
}

- (void)dealloc {
  [requestModel unBindProperty:@"percent" onTarget:self toKeyPath:@"percentUploaded"];   
  [requestModel release];
  [super dealloc];
}
@end

Using the magic of “MAZeroingWeakRef”:https://github.com/mikeash/MAZeroingWeakRef/ we can further reduce the complexity by removing the need to unbind the property in the dealloc method.

@implementation MySlider
@synthesize requestModel;
@synthesize percent;

- (id)init {
  if((self = [super init])) {
    [requestModel bindProperty:@"percent" onTarget:self toKeyPath:@"percentUploaded"];   
  }
  return self;
}

- (void)dealloc {
  [requestModel release];
  [super dealloc];
}
@end

*Hackery Notes:*

“Key Path Bindings”:https://github.com/dewind/KeyPathBindings creates custom subclasses on observed objects in order to generate KVO and dealloc code. Which means it does _not_ currently play nice with objects that have already been subjected to KVO. KVO performs its own runtime trickery that makes subclassing that kind of object relatively complex (See “here”:http://www.mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html and “here”:https://github.com/mikeash/MAZeroingWeakRef/blob/master/Source/MAZeroingWeakRef.m#L447).

If you want to dive into the code you can see it “here”:https://github.com/dewind/KeyPathBindings/blob/master/Source/NSObject+KeyPathBindings.m.