title: Implementing a KVO/Bindings-Compliant Bridge-Pattern in Cocoa
body: <p>I'm trying to implement a simple object bridge in cocoa where the bridge object acts as a kvo/bindings-compliant drop in for some arbitrary other NSObject instance.</p>
  
  <p>Here is my problem (more details in the code below):</p>
  
  <p>A bridge object acts as a drop in for a Person-Object, with an NSString* property called <em>name</em> and an Address* property <em>address</em>. Binding to the keyPath "name" or "address" of the Bridge works nicely. Trouble starts when  binding some object to the keyPath "address.street" of the bridge and a new Address-Object is set for Person's <em>address</em> property. That results in KVO-related exceptions that look like this:</p>
  
  <blockquote>
    <p><code>Cannot remove an observer &lt;NSKeyValueObservance 0x126b00&gt; for the key path "street" from &lt;Address 0x12f1d0&gt; because it is not registered as an observer</code></p>
  </blockquote>
  
  <p>This happens even though the bridge notices the change in the "address"-Property and emits a willChangeValueForKeyPath/didChangeValueForKeyPath tuple.</p>
  
  <p>The code below produces the the problem. It's self-contained objective-c code that can be saved in a file "BridgeDemo.m" and compiled run with</p>
  
  <pre><code>gcc -o test BridgeDemo.m -framework AppKit -framework Foundation; ./test
  </code></pre>
  
  <p>If you know a solution to this problem or can offer me a better approach solving the same problem you make me a <strong>very</strong> happy programmer!</p>
  
  <p>BridgeDemo.m:</p>
  
  <pre><code>#import &lt;Foundation/Foundation.h&gt;
  #import &lt;AppKit/AppKit.h&gt;
  
  /* --- Address ----------------------------------------- */
  
  @interface Address : NSObject {
      NSString* street;
      NSNumber* zipCode;
      NSString* city;
  }
  
  @property(retain) NSString* street;
  @property(retain) NSNumber* zipCode;
  @property(retain) NSString* city;
  
  @end
  
  @implementation Address
  
  @synthesize street, zipCode, city;
  
  -(id)init {
      if( !( self = [super init] ) ) { return nil; }
  
      self.street  = @"Elm Street";
      self.zipCode = @"12345";
      self.city    = @"Crashington";
  
      return self;
  }
  
  -(void) modifyStreet {
      self.street = @"Main Street";
  }
  
  -(void)dealloc { 
      [street release]; [zipCode release]; [city release]; 
      [super dealloc];
  }
  @end
  
  /* --- Person ----------------------------------------- */
  
  @interface Person : NSObject {
      NSString* name;
      Address* address;
  }
  @property(retain) NSString* name;
  @property(retain) Address* address;
  @end
  
  @implementation Person
  
  @synthesize address, name;
  
  -(id)init {
      if( !( self = [super init] ) ) { return nil; }
  
      self.name = @"Tom";
      self.address = [[Address new] autorelease];
  
      return self;
  }
  
  - (void)modifyAddress {
      Address* a = [[Address new] autorelease];
      a.street  = @"Jump Street";
      a.zipCode = @"54321";
      a.city    = @"Memleakville";
      self.address = a;
  }
  
  - (void)dealloc { [address release]; [name release]; [super dealloc]; }
  
  @end
  
  /* --- Bridge ----------------------------------------- */
  
  @interface Bridge : NSObject {
      NSMutableDictionary* observedKeys;
      NSObject* obj;
  }
  
  @property(retain) NSObject* obj;
  
  @end
  
  @implementation Bridge
  
  @synthesize obj;
  
  - (id)init {
      if( !( self = [super init] ) ) { return nil; }
      observedKeys = [NSMutableDictionary new];
      return self;
  }
  - (void)forwardInvocation:(NSInvocation*)inv {
      [inv invokeWithTarget:obj];
  }
  
  - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
      return [obj methodSignatureForSelector:aSelector];
  }
  
  - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  {
      NSLog( @"&gt;&gt;&gt;&gt; Detected Change in keyPath: %@", keyPath );
      [self willChangeValueForKey:keyPath];
      [self didChangeValueForKey:keyPath];    
  }
  
  -(id)valueForUndefinedKey:(NSString*)key {
      /* Register an observer for the key, if not already done */
      if( ![observedKeys objectForKey:key] ) {
          [observedKeys setObject:[NSNumber numberWithBool:YES] forKey:key];
          [obj addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
      } 
      return [obj valueForKey:key];
  }
  
  - (void)dealloc {
      for( NSString* key in [observedKeys allKeys] ) {
          [obj removeObserver:self forKeyPath:key];
      }
      [obj release];
      [observedKeys release];
      [super dealloc];
  }
  
  @end
  
  /* --- MyObserver ------------------------------------ */
  
  @interface MyObserver : NSObject {
      Address* address;
      NSString* street;
  }
  
  @property(retain) Address* address;
  @property(retain) NSString* street;
  @end
  
  @implementation MyObserver
  
  @synthesize street, address;
  
  -(void)dealloc { [street release]; [super dealloc]; }
  
  @end
  
  
  /* This works fine */
  void testBindingToAddress() {
      NSLog( @"Testing Binding to 'address' --------------" );
       NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
      Bridge* b = [[Bridge new] autorelease];
      b.obj = [Person new];
      MyObserver* o = [[MyObserver new] autorelease];
      [o bind:@"address" toObject:b withKeyPath:@"address"
          options:nil];
      NSLog( @"Before modifyStreet: %@", o.address.street );    
      [[b valueForKey:@"address"] performSelector:@selector(modifyStreet)];
      NSLog( @"After modifyStreet: %@", o.address.street );        
  
      [b performSelector:@selector(modifyAddress)];
      NSLog( @"After modifyAdress:  %@", o.address.street );
  
      [pool drain];   
  }
  
  /* This produces an exception */
  void testBindingToStreet() {
      NSLog( @"Testing Binding to 'address.street' --------------" );    
       NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
      Bridge* b = [[Bridge new] autorelease];
      b.obj = [Person new];
      MyObserver* o = [[MyObserver new] autorelease];
      [o bind:@"street" toObject:b withKeyPath:@"address.street"
          options:nil];
  
      NSLog( @"Before modifyStreet: %@", o.street );    
      [[b valueForKey:@"address"] performSelector:@selector(modifyStreet)];
      NSLog( @"After modifyStreet: %@", o.street );        
  
      [b performSelector:@selector(modifyAddress)];
      NSLog( @"After modifyAdress:  %@", o.street );
  
      [pool drain];   
  }
  
  /* --- main() ------------------------------------ */
  int main (int argc, const char * argv[]) {
      testBindingToAddress();
      testBindingToStreet();    
      return 0;
  }
  </code></pre>