We're hiring!

We're actively seeking developers for our new Detroit location. Learn more

Objective-C KeyPath Bindings

Light through a keyholeOne of the nicer features surrounding Objective-C are Key Value Observation (KVO) and Key Value Coding. 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 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 (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 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 and here).

If you want to dive into the code you can see it here.

Justin DeWind (42 Posts)

This entry was posted in Objective C and tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.