Key-value observing—key path confusion

3 views
Skip to first unread message

Andy Balholm

unread,
Jul 2, 2009, 11:30:32 AM7/2/09
to cocotr...@googlegroups.com
If I try to observe several key paths of the same object, all starting with the same key, the notifications are mixed up. No matter which key path changes, the notification says that it was the last one registered.

The problem is that, when registering an observer for a key path, the implementation looks it up by the initial key and the observing object, but not by the whole key path. So it gets the same _NSObservationInfo object each time, and just updates its key path to the new one instead of creating a new _NSObservationInfo.

What we need to do is have it check for a match of key path as a second condition of the if statements that check if the observers match.

In NSKeyValueObserving.m
In NSObject (KeyValueObserving) 

in addObserver:forKeyPath:options:context:

// find if already observing
   _NSObservationInfo *oldInfo=nil;
   for(_NSObservationInfo *current in observers) {
    if(current->observer==observer && [[current keyPath] isEqualToString:keyPath]) {
         oldInfo=current;
         break;
      }
   }

in removeObserver:forKeyPath:

for(_NSObservationInfo *info in [[observers copy] autorelease])
{
  if(info->observer==observer && [[info keyPath] isEqualToString:keyPath])
  {
    [[info retain] autorelease];
    [observers removeObject:info];


P.S. Here is an example program to demonstrate the problem:

#import <Foundation/Foundation.h>

/* This should print:
address.street changed.
address.city changed.

But it prints:
address.city changed.
address.city changed.
*/

@interface Observer : NSObject
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context;
@end


int main (int argc, const char * argv[]) 
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool allocinit];
  
  Observer * o = [[[Observer allocinitautorelease];
  NSMutableDictionary * dict = [NSMutableDictionary dictionary];
  NSMutableDictionary * address = [NSMutableDictionary dictionary];
  [address setObject:@"One Infinite Loop" forKey:@"street"];
  [address setObject:@"Cupertino" forKey:@"city"];
  [dict setObject:address forKey:@"address"];
  
  [dict addObserver:o forKeyPath:@"address.street" options:0 context:NULL];
  [dict addObserver:o forKeyPath:@"address.city" options:0 context:NULL];
  
  [address setObject:@"345 Park Ave." forKey:@"street"];
  [address setObject:@"San Jose" forKey:@"city"];
  
  [pool drain];
  return 0;
}


@implementation Observer

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  NSLog(@"%@ changed.", keyPath);
}

@end


Johannes Fortmann

unread,
Jul 4, 2009, 5:45:16 AM7/4/09
to Cocotron Developers
I've committed this in r532. Thanks!
Reply all
Reply to author
Forward
0 new messages