ブリッジオブジェクトが任意の他の NSObject インスタンスの kvo/bindings 準拠のドロップインとして機能する単純なオブジェクトブリッジをココアに実装しようとしています。
ここに私の問題があります(以下のコードの詳細):
ブリッジ オブジェクトは、name という名前の NSString* プロパティとAddress* プロパティaddressを使用して、Person-Object のドロップインとして機能します。Bridge の keyPath の「名前」または「アドレス」へのバインドはうまく機能します。ブリッジの keyPath "address.street" に何らかのオブジェクトをバインドし、新しい Address-Object が Person のaddressプロパティに設定されると、問題が発生します。その結果、次のような KVO 関連の例外が発生します。
Cannot remove an observer <NSKeyValueObservance 0x126b00> for the key path "street" from <Address 0x12f1d0> because it is not registered as an observer
これは、ブリッジが「address」プロパティの変更を認識し、willChangeValueForKeyPath/didChangeValueForKeyPath タプルを発行した場合でも発生します。
以下のコードは問題を引き起こします。ファイル「BridgeDemo.m」に保存し、コンパイルして実行できる自己完結型の目的の C コードです。
gcc -o test BridgeDemo.m -framework AppKit -framework Foundation; ./test
この問題の解決策を知っているか、同じ問題を解決するためのより良いアプローチを提供してくれるなら、私はとても幸せなプログラマーになります!
BridgeDemo.m:
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
/* --- 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( @">>>> 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;
}