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