gpt4 book ai didi

cocoa - 在 Cocoa 中实现符合 KVO/绑定(bind)的桥接模式

转载 作者:行者123 更新时间:2023-12-03 16:08:56 25 4
gpt4 key购买 nike

我正在尝试在 cocoa 中实现一个简单的对象桥,其中桥对象充当任意其他 NSObject 实例的 kvo/绑定(bind)兼容的下降。

这是我的问题(更多详细信息请参见下面的代码):

桥对象充当 Person-Object 的替代者,具有名为 name 的 NSString* 属性和一个名为 address 的 Address* 属性。绑定(bind)到桥的 keyPath“名称”或“地址”效果很好。当将某个对​​象绑定(bind)到桥的 keyPath“address.street”并且为 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

即使桥注意到“地址”属性的更改并发出 willChangeValueForKeyPath/didChangeValueForKeyPath 元组,也会发生这种情况。

下面的代码产生了问题。它是独立的 Objective-C 代码,可以保存在文件“BridgeDemo.m”中并使用编译运行

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

最佳答案

问题是这样的:

[self willChangeValueForKey:keyPath]; <--- 此时实际观察者需要取消订阅 street [self didChangeValueForKey:keyPath]; <--- 并将其自身添加到新值中。

如果不提供新值,您就剥夺了观察者取消订阅的机会。

这是一个可以运行并演示问题的破解版本。

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

关于cocoa - 在 Cocoa 中实现符合 KVO/绑定(bind)的桥接模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/204125/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com