如何调试KVO

在我的程序中,我手动使用KVO来观察对象属性值的更改。 我在自定义设置器的下面一行代码中收到EXC_BAD_ACCESS信号:

[self willChangeValueForKey:@"mykey"];

奇怪的是,这种情况发生在工厂方法调用自定义setter时,并且不应该有任何旁观者。 我不知道如何调试这种情况。

更新:列出所有注册观察者的方式是observationInfo 。 事实证明,确实列出了一个指向无效地址的对象。 但是,我完全不知道它是如何到达那里的。

更新2:显然,对于给定对象,可以多次注册同一对象和方法回调 - 从而导致观察对象的observationInfo中的条目相同。 删除注册时,只有其中一个条目被删除。 这种行为有点违反直觉(在我的程序中肯定是添加多个条目的bug),但这并不能解释虚假的观察者如何神秘地显示在新分配的对象中(除非有一些缓存/重用继续我不知道)。

修改后的问题: 我怎样才能弄清楚哪里和什么时候一个对象被注册为观察者?

更新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"];
}

容器类具有类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:我发现使用混合的断点, NSLog ,代码审查和出汗的错误。 我没有在KVO中使用上下文,但这绝对是另一个有用的建议。 这确实是一个错误的双重登记,因为我无法理解的原因导致了观察到的行为。

实现包括[self willChange...]; [self didChange...] [self willChange...]; [self didChange...]按照描述(在iOS 5上)运行,尽管它远非漂亮。 问题是,由于NSArray不符合KVO标准,因此无法谈论其内容的更改。 我也曾想过Mike Ash提出的通知,但我决定和KVO一起去,因为这看起来像是一个更Cocoa思的机制来完成这项工作。 这可能不是最好的决定......


为了响应您的修改后的问题,请尝试在您的自定义类中覆盖addObserver:forKeyPath:options:context:并在其上设置断点。 另外,你可以在-[NSObject addObserver:forKeyPath:options:context:]上设置一个符号断点,但这可能会受到很大打击。


是的,调用-addObserver:两次将导致两次注册。 类Foo和Foo,Bar的某个子类可以(合法地)注册相同的通知,但具有不同的上下文(始终包含上下文,始终在-observeValueForKeyPath检查上下文,并始终在-observeValueForKeyPath调用super)。

这意味着Bar的一个实例将会注册两次,这是正确的。

但是,你几乎肯定不会不小心注册同一个对象/ keypath / context,并且@wbyoung说覆盖-addObserver:forKeyPath:options:context:应该帮助你确保这不会发生。 如果需要跟踪数组中的observers / keypath / context并确保它们是唯一的。

Mike Ash在他的博客中有一些有趣的想法和代码,关于使用上下文。 他说的是正确的,但实际上KVO是完全可用的。

也就是说,当你用它来做某件事时,它意味着待办事项。 它曾经是你绝对不能做这样的事情..

[self willChangeValueForKey:@"contents"];
[self didChangeValueForKey:@"contents"];

因为这是一个谎言。 当您调用-willChange..时,“内容”的值必须与您调用-didChange..时的值不同。 KVO机制将在-willChangeValueForKey-didChangeValueForKey调用-valueForKey:@"contents"以验证值已更改。 这显然不会对数组起作用,因为无论如何修改内容,您仍然拥有相同的对象。 现在我不知道这是否仍然是这种情况(web搜索没有出现),但请注意-willChangeValueForKey, -didChangeValueForKey不是处理集合的手动kvo的正确方法。 为此,Apple提供了其他方法: -

– willChange:valuesAtIndexes:forKey:
– didChange:valuesAtIndexes:forKey:
– willChangeValueForKey:withSetMutation:usingObjects:
– didChangeValueForKey:withSetMutation:usingObjects:

价值必须改变也许并不是真的,但如果是这样,你的方案不会奏效。

我会做的是有一个修改你的收藏通知。 还有一个不同的通知,用于修改该集合中的项目。 即当你试图触发@“内容”的通知时,你可以拥有@“contents”和@“propertiesOfContents”。 您需要观察两个keypath,但您可以使用自动kvo而不是手动触发通知。 (使用自动kvo将确保调用正确版本的-willChange.. -didChange..

对于一个数组的自动kvo来看看(不需要NSArrayController): - 键值 - 观察可可中的一对多关系

然后每次将项目添加到集合中时,观察您需要的属性(如您现在所做的那样),并在它们更改时为self.propertiesOfContents翻转一个值。 (好吧,当我读回来,它不一定听起来不像你的解决方案,但我仍然相信它可能表现更好)。

链接地址: http://www.djcxy.com/p/47339.html

上一篇: How to debug KVO

下一篇: Write in C++ and expose to C# or write directly in C#?