6

Estoy tratando de implementar un puente de objeto simple en cacao donde el objeto puente actúa como una caída compatible con kvo/bindings para alguna otra instancia arbitraria de NSObject.Implementando un puente-patrón compatible con KVO/Bindings en Cocoa

Aquí es mi problema (más detalles en el código de abajo):

Un puente objeto actúa como una gota en una persona a objetos, con una NSString * propiedad llamada nombre y una dirección * propiedad dirección. La vinculación al "nombre" o "dirección" keyPath del Puente funciona muy bien. El problema se inicia al vincular un objeto al keyPath "address.street" del puente y se establece un nuevo Address-Object para la propiedad de la persona. Eso da lugar a excepciones relacionadas con el MVA que se ven así:

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

Esto sucede a pesar de que el puente se da cuenta del cambio en la "dirección" -Property y emite una tupla willChangeValueForKeyPath/didChangeValueForKeyPath.

El siguiente código produce el problema. Es código de Objective-C autónomo que se pueden guardar en un archivo "BridgeDemo.m" y compilado carrera con

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

Si conoce una solución a este problema o me puede ofrecer un mejor enfoque para resolver el mismo problema me haces un programador feliz muy!

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

Respuesta

7

aquí está el problema:

[auto willChangeValueForKey: keyPath]; < --- En este momento, el observador real necesita darse de baja en la calle [self didChangeValueForKey: keyPath]; < --- y agregarse al nuevo valor.

Al no proporcionar el nuevo valor, le está negando al observador la oportunidad de darse de baja.

Aquí hay una versión pirateada que funciona y demuestra el problema.

/* --- 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]; 
} 
.... 
......