【发布时间】:2011-11-11 11:16:38
【问题描述】:
在我的程序中,我手动使用 KVO 来观察对象属性值的变化。我在自定义设置器中的以下代码行收到EXC_BAD_ACCESS 信号:
[self willChangeValueForKey:@"mykey"];
奇怪的是,当工厂方法调用自定义设置器并且周围不应该有任何观察者时,就会发生这种情况。我不知道如何调试这种情况。
更新:列出所有注册观察者的方式是observationInfo。事实证明,确实列出了一个指向无效地址的对象。但是,我完全不知道它是如何到达那里的。
更新 2: 显然,可以为给定对象多次注册相同的对象和方法回调 - 导致观察对象的 observationInfo 中的条目相同。删除注册时,仅删除其中一个条目。这种行为有点违反直觉(添加多个条目肯定是我的程序中的一个错误),但这并不能解释虚假观察者如何神秘地出现在新分配的对象中(除非有一些缓存/重用正在发生,我不知道)。
修改后的问题:我怎样才能确定对象在何处以及何时注册为观察者?
更新 3: 具体示例代码。
ContentObj 是一个类,它有一个字典作为名为@987654326@ 的属性。它覆盖:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"mykey"]) {
automatic = NO;
} else {
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
一些属性具有如下的 getter 和 setter:
- (CGFloat)value {
return [[[self mykey] objectForKey:@"value"] floatValue];
}
- (void)setValue:(CGFloat)aValue {
[self willChangeValueForKey:@"mykey"];
[[self mykey] setObject:[NSNumber numberWithFloat:aValue]
forKey:@"value"];
[self didChangeValueForKey:@"mykey"];
}
容器类具有类NSMutableArray 的属性contents,它包含类ContentObj 的实例。它有几个手动处理注册的方法:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"contents"]) {
automatic = NO;
} else {
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
- (void)observeContent:(ContentObj *)cObj {
[cObj addObserver:self
forKeyPath:@"mykey"
options:0
context:NULL];
}
- (void)removeObserveContent:(ContentObj *)cObj {
[cObj removeObserver:self
forKeyPath:@"mykey"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (([keyPath isEqualToString:@"mykey"]) &&
([object isKindOfClass:[ContentObj class]])) {
[self willChangeValueForKey:@"contents"];
[self didChangeValueForKey:@"contents"];
}
}
容器类中有几个方法可以修改contents。它们如下所示:
- (void)addContent:(ContentObj *)cObj {
[self willChangeValueForKey:@"contents"];
[self observeDatum:cObj];
[[self contents] addObject:cObj];
[self didChangeValueForKey:@"contents"];
}
还有其他几个提供与数组类似的功能。他们都通过添加/删除自己作为观察者来工作。显然,导致多次注册的任何事情都是一个错误,并且可能隐藏在这些方法中某处。
我的问题针对如何调试这种情况的策略。或者,请随时提供实现这种通知/观察者模式的替代策略。
更新 4: 我使用断点、NSLogs、代码审查和出汗的混合方式发现了这个错误。我没有在 KVO 中使用上下文,尽管这绝对是另一个有用的建议。这确实是一个错误的双重注册——由于我无法理解的原因——导致了观察到的行为。
包括[self willChange...]; [self didChange...] 在内的实现如所描述的那样工作(在iOS 5 上),尽管它远非漂亮。问题在于NSArray 不符合 KVO,因此无法谈论对其内容的更改。我也考虑过 Mike Ash 建议的通知,但我决定使用 KVO,因为这似乎更像是一种Cocoa-ish 机制来完成这项工作。这可以说不是最好的决定......
【问题讨论】:
-
一些代码会很有用.. 你在哪里添加你的观察者?为什么要发送自定义通知? myKey 的 getter 和 setter 是什么样的?你在用 Zombies 工具来查看它崩溃的地方吗?
-
@hooleyhoop:我正在发送自定义通知,因为我使用一对多关系(
NSMutableArray)并且希望在 1)数组被修改(添加/删除对象)时收到通知/rearranged) 和 2) 数组内的对象被修改。由于 Cocoa Touch 没有NSArrayController,这似乎是唯一的可能性。 -
@hooleyhoop:我曾尝试使用 Zombies 工具,但它并没有太大帮助,因为问题与 KVO 有关,而不是直接与引用计数有关。我只观察到当我不使用 Zombies 时它会崩溃,而当我使用时它不会崩溃。 Zombies 无法帮助我识别导致崩溃的对象,因为崩溃与在 KVO 中注册的对象有关,而不是与
release相关。 -
嗯,您不需要手动触发任何通知来执行此操作,但这并不是说如果您这样做就会崩溃。您是否将观察者添加到数组中?子类化一个数组?很多问题.. 不看代码就无法回答。
-
我现在已经发布了具体的代码。最初,我正在寻找有关如何调试此场景的策略。由于在数百行这样的代码中某处存在错误,我认为不太可能有人可以帮助我找到它(毕竟,这是我的工作!)。所以我主要是在确定工具和策略方面寻求帮助——比如
observationInfo结构和我不知道的类似事情。
标签: iphone objective-c cocoa-touch key-value-observing