如何调试KVO

use*_*472 4 iphone cocoa-touch objective-c key-value-observing

在我的程序中,我手动使用KVO来观察对象属性值的变化.我EXC_BAD_ACCESS在自定义setter中的以下代码行中收到一个信号:

[self willChangeValueForKey:@"mykey"];
Run Code Online (Sandbox Code Playgroud)

奇怪的是,当工厂方法调用自定义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;
}
Run Code Online (Sandbox Code Playgroud)

有几个属性有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"];
}
Run Code Online (Sandbox Code Playgroud)

容器类有一个类的属性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"];
    }
}
Run Code Online (Sandbox Code Playgroud)

容器类中有几种方法可以修改contents.他们看起来如下:

- (void)addContent:(ContentObj *)cObj {
    [self willChangeValueForKey:@"contents"];
    [self observeDatum:cObj];
    [[self contents] addObject:cObj];
    [self didChangeValueForKey:@"contents"];
}
Run Code Online (Sandbox Code Playgroud)

以及其他一些为阵列提供类似功能的人.他们都通过添加/删除自己作为观察者来工作.显然,任何结果都会对多个注册是一个错误,可以坐在某个地方隐藏在这些方法.

我的问题针对如何调试这种情况的策略.或者,请随意提供实施此类通知/观察员模式的替代策略.

更新4:我发现了使用断点NSLog,代码评论和出汗的混合错误.我没有在KVO中使用上下文,尽管这绝对是另一个有用的建议.确实是一个错误的双重注册 - 由于我无法理解的原因 - 导致观察到的行为.

实现包括[self willChange...]; [self didChange...]所描述的工作(在iOS 5上),虽然它远非美丽.问题是,由于NSArray不符合KVO标准,因此无法谈论其内容的变化.我也考虑过Mike Ash所建议的通知,但我决定选择KVO,因为这似乎是一种更为Cocoa机制的工作.这可能不是最好的决定......

hoo*_*oop 6

是的,-addObserver:两次通话将导致两次注册.类Foo和Foo,Bar的一些子类可以(合法地)注册相同的通知,但是具有不同的上下文(总是包括上下文,总是检查上下文-observeValueForKeyPath并且总是调用super -observeValueForKeyPath).

这意味着Bar的实例将注册两次,这是正确的.

但是,您几乎肯定不希望意外地多次注册相同的对象/ keypath/context,并且@wbyoung说覆盖-addObserver:forKeyPath:options:context:应该帮助您确保不会发生这种情况.如果nesessary跟踪数组中的观察者/ keypath/context并确保它们是唯一的.

Mike Ash在他的博客上有一些关于使用上下文的有趣想法和代码.他说它被打破是对的,但在实践中KVO是完全可用的.

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

[self willChangeValueForKey:@"contents"];
[self didChangeValueForKey:@"contents"];
Run Code Online (Sandbox Code Playgroud)

因为这是谎言 调用时"内容"的值-willChange..必须与调用时的值不同-didChange...KVO机制将调用-valueForKey:@"contents"两者-willChangeValueForKey-didChangeValueForKey验证值是否已更改.这显然不适用于数组,因为无论您如何修改内容,您仍然拥有相同的对象.现在我不知道是否仍然如此(网络搜索没有发现),但请注意,-willChangeValueForKey, -didChangeValueForKey 这不是处理集合的手动kvo的正确方法.为此Apple提供了替代方法: -

– willChange:valuesAtIndexes:forKey:
– didChange:valuesAtIndexes:forKey:
– willChangeValueForKey:withSetMutation:usingObjects:
– didChangeValueForKey:withSetMutation:usingObjects:
Run Code Online (Sandbox Code Playgroud)

价值必须改变可能仍然不是真的,但如果是,那么你的方案就无法发挥作用.

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

对于数组的自动kvo,请看一下(不需要NSArrayController): - 键值 - 在Cocoa中观察到多对多的关系

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


wby*_*ung 4

为了回答您修改后的问题,请尝试在自定义类中覆盖addObserver:forKeyPath:options:context:并在其上设置断点。或者,您可以在 上设置一个符号断点-[NSObject addObserver:forKeyPath:options:context:],但这可能会经常被击中。