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 alloc] init];
Observer * o = [[[Observer alloc] init] autorelease];
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