6

Ich versuche, eine einfache Objektbrücke in Kakao zu implementieren, wo das Brückenobjekt als eine kvo/bindings-konforme Einfügung für eine beliebige andere NSObject-Instanz fungiert.Implementieren eines KVO/Bindings-Compliant Bridge-Patterns in Cocoa

Hier ist mein Problem (mehr Details im Code unten):

Eine Brücke Objekt wirkt wie ein Tropfen in einer Person-Objekt mit einer Eigenschaft NSString * Name und eine Adresse * Eigenschaft genannt Adresse. Die Bindung an den keyPath "Name" oder "Adresse" der Bridge funktioniert gut. Die Problembehandlung beginnt, wenn ein Objekt an den keyPath "address.street" der Bridge gebunden wird und ein neues Adressobjekt für die Adresse der Person festgelegt wird. Das ergibt KVO bedingte Ausnahmen, die wie folgt aussehen:

Cannot remove an observer <NSKeyValueObservance 0x126b00> for the key path "street" from <Address 0x12f1d0> because it is not registered as an observer

Dies geschieht, obwohl die Brücke bemerkt die Änderung in der „Adresse“ -Grundstück und gibt ein willChangeValueForKeyPath/didChangeValueForKeyPath Tupels.

Der folgende Code erzeugt das Problem. Es ist in sich geschlossene Objective-C-Code, der in einer Datei „BridgeDemo.m“ und kompiliert Lauf mit

gcc -o test BridgeDemo.m -framework AppKit -framework Foundation; ./test 

gespeichert werden können Wenn Sie eine Lösung für dieses Problem kennen oder kann mir einen besseren Ansatz bieten die gleiche Problemlösung Sie machen mich ein sehr glücklich Programmierer!

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; 
} 

Antwort

7

Hier ist das Problem:

[self willChangeValueForKey: keyPath]; < --- Zu diesem Zeitpunkt muss sich der aktuelle Beobachter von der Straße abmelden [self didChangeValueForKey: keyPath]; < --- und fügen Sie sich zu dem neuen Wert hinzu.

Indem Sie den neuen Wert nicht angeben, verweigern Sie dem Beobachter die Möglichkeit, sich abzumelden.

Hier ist eine gehackte Version, die funktioniert und das Problem demonstriert.

/* --- Bridge ----------------------------------------- */ 
.... 
..... 

@interface Bridge : NSObject { 
    NSMutableDictionary* observedKeys; 
    NSObject* obj; 

    //**** Dictionary for old values just before we send the didChangeValue notification. 
    NSMutableDictionary * oldValues; 
} 

... 
..... 

- (id)init { 
    if(!(self = [super init])) { return nil; } 
    observedKeys = [NSMutableDictionary new]; 
    //************* Initialize the new dictionary 
    oldValues = [NSMutableDictionary new]; 
    return self; 
} 
.... 
..... 

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{ 
    NSLog(@">>>> Detected Change in keyPath: %@", keyPath); 
    // **** Cache the old value before telling everyone its going to change. 
    [oldValues setValue:[change valueForKey:NSKeyValueChangeOldKey] forKey:keyPath]; 
    [self willChangeValueForKey:keyPath]; 
    // **** Simulate the change by removing the old value. 
    [oldValues removeObjectForKey:keyPath]; 
    // **** Now when we say we did change the value, we are not lying. 
    [self didChangeValueForKey:keyPath];  
} 

-(id)valueForUndefinedKey:(NSString*)key { 
    // **** Important part, return oldvalue if it exists 
    id oldValue; 
    if(oldValue = [oldValues valueForKey:key]){ 
     return oldValue; 
    } 
    /* Register an observer for the key, if not already done */  
    if(![observedKeys objectForKey:key]) { 
     [observedKeys setObject:[NSNumber numberWithBool:YES] forKey:key]; 
     NSLog(@"adding observer for:%@", key); 
     [obj addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 
    } 
    return [obj valueForKey:key]; 
} 
.... 
...... 
Verwandte Themen