Two-Way Data Binding in Cocoa

Coming from the .NET world, it seems strange to me that for whatever reason, bi-directional data binding isn’t supported out of the box in Cocoa.

The various provided standard controllers effectively implement two-way data binding. Take for example NSArrayController's contentArray. When binding NSArrayController's contentArray to a collection in your own data model, the NSArrayController uses Cocoa’s KVO based binding technology to observe changes to your data model. Coming to Cocoa with experience from other data binding technologies, this is pretty straightforward and expected behavior.

NSArrayController also pushes updates to your data model, but not through Cocoa’s binding technology. In the reverse direction, NSArrayController caches the information passed to it when a data binding is established. This cached information is used to force feed updated values to the source of the data binding via Cocoa’s KVC technology. It wasn’t initially clear to me that this was being accomplished “manually” by the NSArrayController and that this bi-directional behavior wasn’t something I could expect to happen automatically when implementing my own controllers.

Apple’s documentation doesn’t touch on strategies to implement the kind of two way communication that their standard controllers evidence that I could find. In fact, Cocoa Bindings Programming Topics doesn’t really have a lot to say on the subject of implementing your own controllers at all. As usual, I found solace via Google and the Cocoa community. In particular I found this clear and brief CocoaHeads México presentation to be a really helpful addition to Apple’s documentation when getting up to speed on implementing custom controllers.

Fortunately, Apple’s sample code does a great job of showing an example of implementing a custom controller that performs this kind of two way communication. The BindingsJoysticks sample demonstrates the pattern of overriding your custom controller’s bind implementation and caching the source object and source key path of the bind.

An advantage of the “manual” implementation of the back flow of data from controller to source in Apple’s sample code is a high degree of flexibility and control over exactly when the source of the data bind is updated by your controller. In fact, you can choose to have your source object and your controller be out of sync from each other for a period of time (or forever) if that floats your boat. This can be pretty useful in various situations (interactive mousing with undo/redo comes to mind), but can also be harder to maintain and downright cryptic to a consumer of the controller.

A fairly obvious downside to “manually” implementing the back flow of data from controller to source is, well, the manual part. The extra typing isn’t so bad if you are implementing your one true controller to rule them all, but if you have a number of properties you want to bind, it can be pretty tedious. Consider for example, implementing in the style of Model-View-View/Model (MVVM).

In MVVM, it’s typical to use data binding to glue your view to your view model without either being the wiser. Having not built a large scale Windows Presentation Framework (WPF) application (whose API is architected around the MVVM pattern) I never really got the Kool-Aid pumping through my veins. Proponents of MVVM believe it makes it easier to iterate on user interface, easier to perform headless automated testing of UI functionality, and provides overall more scalable and maintainable implementations. If you are interested in learning more about MVVM, some links to check out are:

In MVVM, there are often a relatively large number of properties across a number of objects that need to be bound to each other. Manually implementing two-way data binding for each such object can be kind of a drag. For that reason, I threw together a little utility class to handle the back flow of values from controller to source (or View to View-Model) for me. The sample code is available at Cocoa Convert on Google Code.

A few caveats before I go on: this is definitely not production code and I use garbage collection. In other words, no effort has been put into retaining objects appropriately and it’s probably a very bad idea to drop this into your own project as is.

An instance of the TwoWayBindingManager class is owned by your custom controller:

#import "TwoWayBindingManager.h"

@interface MyController : NSObject 
{
@private
  ...
  TwoWayBindingManager* _twoWayBindingManager;

It is instantiated when your controller is initialized, and should be disposed of when your controller is disposed of (either in a custom dispose method, or when not using garbage collection in a dealloc):

- (id)init
{
  if([super init] == nil)
    return nil;

  _twoWayBindingManager = 
    [[TwoWayBindingManager alloc] initWithObject:self];

  return self;
}

- (void)dispose
{
  [_twoWayBindingManager dispose];
  _twoWayBindingManager = nil;
}

Your custom controller then needs to override bind and unbind and give the TwoWayBindingManager instance a chance to hook in:

- (void)bind:(NSString *)bindingName
    toObject:(id)observableController
 withKeyPath:(NSString *)keyPath
     options:(NSDictionary *)options
{	
  [super bind:bindingName
	 toObject:observableController
  withKeyPath:keyPath
      options:options];

  [_twoWayBindingManager bind:bindingName
					 toObject:observableController
				  withKeyPath:keyPath
					  options:options];
}

- (void)unbind:bindingName
{
  [super unbind:bindingName];
  [_twoWayBindingManager unbind:bindingName];
}

From the controller side, that’s it! As far as usage goes, you just need to pass in the extra option kWantsTwoWayBinding for any bindings that you want to be bi-directional:

[cont bind:@"someControllerProperty" 
  toObject:self 
 withKeyPath:@"someSelfProperty"
   options:[NSDictionary dictionaryWithObjectsAndKeys:
  		    [NSNumber numberWithBool:YES], 
		    kWantsTwoWayBinding, nil]];

The major trade-off of the TwoWayBindingController approach is less flexibility and control over how closely synchronized the properties of your custom controller are kept to your data source. For situations where you are willing to trade that kind of control for the ease of bi-directional data binding, an approach like the TwoWayBindingController may just save you a lot of typing and bug hunting!

The sample code accompanying this article is available at Cocoa Convert on Google Code.

About these ads
Published in: on May 31, 2009 at 10:16 pm  Comments (3)  
Tags: , , ,

The URI to TrackBack this entry is: http://cocoaconvert.net/2009/05/31/two-way-data-binding-in-cocoa/trackback/

RSS feed for comments on this post.

3 CommentsLeave a comment

  1. I’m not certain I’m reading the intent of the article correctly, but it’s absolutely possible to do two-way bindings out of the box, even with arrays. I uploaded a sample:

    http://idisk.mac.com/sstevenson-Public?view=web
    (direct link: http://files.me.com/sstevenson/p9v901)

    The only thing you can’t do is change the array out from under the normal notification system. You have three options: make a copy and re-set it with the setter method, use -mutableArrayValueForKey:, or to implement indexed accessors.

    NSArrayController may choose to implement something custom, but I don’t see any reason the automatic stuff wouldn’t work for many common cases. I realize I might be missing the point of the article, though. This is just how I read it.

    • Hiya Scott! I guess what I mean in the article is that as far as I can tell in Cocoa, if you modify the target of the bind, the source is not updated, only the other way around.

      For example, if I add the second addObject call to the addItem method like this:

      - (IBAction)addItem:(id)sender 
      {    
        [[self mutableArrayValueForKey:@"mySourceArray"] 
          addObject:[NSDate date]];
        [[self.myOtherObject mutableArrayValueForKey:@"myTargetArray"] 
          addObject:[NSDate date]];
        NSLog(@"%s mySourceArray: %@", _cmd, self.mySourceArray);
        NSLog(@"%s myTargetArray: %@", _cmd, self.myOtherObject.myTargetArray);    
      }
      

      The output to the console is:


      2009-06-02 21:45:35.204 TwoWayBindings[715:813] addItem: mySourceArray: (
      2009-06-02 21:45:35 -0700
      )
      2009-06-02 21:45:35.206 TwoWayBindings[715:813] addItem: myTargetArray: (
      2009-06-02 21:45:35 -0700,
      2009-06-02 21:45:35 -0700
      )

      Indicating that the synchronization of the source and target is uni-directional. Adding to the source is reflected in the target, but adding to the target does not affect the source. In XAML, you can specify a “one time”, “one way” or “two way” among other options for how you want the bound properties to update.

      As far as I can tell, in Cocoa, the data bindings are always “one way” meaning that only updates in the source affect the target, and not the other way around.

      I gotta work on being more concise in my blog posts and as a Cocoa neophyte, I am more than prepared to eat my words :)

  2. Jeremy, thank you for your article. Cocoa indeed doesn’t provide any way to make two-way binding and it’s a big pain when custom control is developed. I’ve spent a wondering why does my model ignore UI changes… Also, i found a mistake in your code i believe. There is a method – (void)observeValueForKeyPath: in TwoWayBindingManager and in the bottom of the method there is a line

    if(![[observableController valueForKey:observableKeyPath] isEqual:value])

    This must be “valueForKeyPath”. Or we can ignore this check at all.

    Best!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: