在我的程序中,我手动使用KVO来观察对象属性值的变化。我在自定义设置器中的以下代码行中收到EXC_BAD_ACCESS
信号:
[self willChangeValueForKey:@"mykey"];
奇怪的是,当工厂方法调用自定义setter并且不应该有任何观察者时会发生这种情况。我不知道如何调试这种情况。
更新:列出所有已注册观察员的方式是observationInfo
。事实证明,确实列出了一个指向无效地址的对象。但是,我根本不知道它是如何到达那里的。
更新2:显然,对于给定对象,可以多次注册相同的对象和方法回调 - 导致观察对象的observationInfo
中的条目相同。删除注册时,仅删除其中一个条目。这种行为有点违反直觉(在我的程序中根本就是添加多个条目的错误),但这并不能解释虚假的观察者如何神秘地出现在新分配的对象中(除非有一些缓存/重用)继续,我不知道)。
修改后的问题:如何判断对象何时被注册为观察者?
更新3:特定示例代码。
ContentObj
是一个将字典作为名为mykey
的属性的类。它覆盖:
+ (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"];
}
容器类具有类contents
的属性NSMutableArray
,其中包含类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:我发现了使用断点,NSLog
,代码评论和出汗的混合错误。我没有在KVO中使用上下文,尽管这绝对是另一个有用的建议。这确实是一个错误的双重登记 - 由于我无法理解的原因 - 导致观察到的行为。
包括[self willChange...]; [self didChange...]
在内的实现与iOS 5中描述的一样有效,尽管它远非美观。问题是,由于NSArray
不符合KVO标准,因此无法谈论其内容的更改。我也考虑过Mike Ash建议的通知,但我决定选择KVO,因为这似乎是一个更加Cocoa
- ish机制来完成这项工作。这可能不是最好的决定......
答案 0 :(得分:6)
是的,两次调用-addObserver:
将导致两次注册。类Foo和Foo,Bar的一些子类可以(合法地)注册相同的通知,但是具有不同的上下文(总是包括上下文,总是检查-observeValueForKeyPath
中的上下文并始终在{{1}中调用super }})。
这意味着Bar的实例将注册两次,这是正确的。
但是,您几乎肯定不希望不小心注册相同的对象/密钥路径/上下文,并且@wbyoung说覆盖-observeValueForKeyPath
应该帮助您确保不会发生这种情况。如果nesessary跟踪数组中的观察者/ keypath / context并确保它们是唯一的。
Mike Ash has some interesting thoughts and code on his blog about using contexts。他说它被打破是对的,但在实践中KVO是完全可用的。
也就是说,当你用它来做某事时,这意味着待办事项。曾经是你绝对不能做这样的事情..
-addObserver:forKeyPath:options:context:
因为这是谎言。致电[self willChangeValueForKey:@"contents"];
[self didChangeValueForKey:@"contents"];
时,“内容”的值必须与您致电-willChange..
时的值不同。 KVO机制将调用-didChange..
和-valueForKey:@"contents"
中的-willChangeValueForKey
来验证值是否已更改。这显然不适用于数组,因为无论您如何修改内容,您仍然拥有相同的对象。现在我不知道是否仍然如此(网络搜索没有发现),但请注意-didChangeValueForKey
are not the correct way to handle manual kvo of a collection.因为Apple提供了替代方法: -
-willChangeValueForKey, -didChangeValueForKey
价值必须改变可能仍然不是真的,但如果是,那么你的方案就无法发挥作用。
我要做的是有一个修改您的收藏的通知。以及修改该集合中项目的不同通知。即,当你试图触发@“内容”的通知时,你可能会有@“内容”和@“propertiesOfContents”。您需要观察两个键路径,但您可以使用自动kvo而不是手动触发通知。 (使用自动kvo将确保调用正确版本的– willChange:valuesAtIndexes:forKey:
– didChange:valuesAtIndexes:forKey:
– willChangeValueForKey:withSetMutation:usingObjects:
– didChangeValueForKey:withSetMutation:usingObjects:
)
对于数组的自动kvo,请看一下(不需要NSArrayController): - Key-Value-Observing a to-many relationship in Cocoa
然后,每次将一个项目添加到集合中时,请观察所需的属性(正如您现在所做的那样)以及何时更改为-willChange.. -didChange..
翻转一个值。 (好吧,因为我读回来它并不一定听起来不如你的解决方案,但我仍然相信它可能表现得更好)。