从单元格到托管对象的键值观察器

keyValue observer from cell to managed object

我正在查看此处的 table 视图单元格,我发现了这段代码:

- (void)awakeFromNib {
    [super awakeFromNib];
    [self addObserver:self forKeyPath:@"model.isDownloading" options:NSKeyValueObservingOptionNew context:NULL];
    [self addObserver:self forKeyPath:@"model.isCached" options:NSKeyValueObservingOptionNew context:NULL];
    [self addObserver:self forKeyPath:@"model.isOutDated" options:NSKeyValueObservingOptionNew context:NULL];
    [self addObserver:self forKeyPath:@"model.cacheUpdateDate" options:NSKeyValueObservingOptionNew context:NULL];
    [self addObserver:self forKeyPath:@"model" options:NSKeyValueObservingOptionNew context:NULL];
}

观察者在dealloc方法中被移除。 model 是一个 weak 属性,接收一个托管对象(核心数据)。

我收到虚假崩溃,告诉我管理对象已删除,但仍有观察者注册。

错误发生的原因对我来说很清楚:对象在后台某处被删除,但仍链接到 table 视图的单元格中。由于单元格上的 dealloc 在应用程序的生命周期内基本上从未被调用过,因此从未真正删除观察者。由于对核心数据对象的引用是 weak,它将在后台静默解除分配 - 至少尝试这样做。这失败了,因为模型仍然被观察到。

我有一些问题:

错误信息是:

class xxx was deallocated while key value observers were still registered with it

如果观察到像"model.isDownloading"这样的路径,那么观察者是在model对象中注册的,而不是在self中的setter中,对吗?

这是一个很好的问题。据我之前所知,当一个对象注册到 KVO
时,运行时至少会跟踪两件事 1) 观察的对象和
2) 它正在观察的 属性 的键路径。

我们知道运行时会覆盖 属性 的 setter 以通知对象观察到变化。

但显然运行时还必须跟踪观察的对象,否则它怎么知道它是否在仍有观察者注册的情况下被释放?

运行时似乎解析了 keyPath 的点,并遵循来自接收器的引用链(在您的示例中为 self),以跟踪观察到的对象(self.model)

如果 model 被重新分配,objC 是否足够聪明来处理观察者变化(self.model = newThing 要求 removeObservermodel 之前被调用分配 newThing,然后观察者需要在 newThing 注册。

不,它不够聪明。例如,运行时 subclasses 您的 self.model 对象(假设它的类型为 Model)覆盖 isDownloading 的 setter。现在您的 self.model 对象的类型是 NSKVONotifying_Model。如果您要换出 self.model 指针以指向类型为 Model 的新对象,它将与运行时创建的 KVO class 不同。因此 属性 的 setter 不会添加通知观察者的指令。 所以是的,你必须删除第一个对象上的观察者并将其添加到第二个对象,即使你使用的是相同的指针变量。

由于崩溃发生在托管对象的 dealloc 上,我认为一个简单的解决方案是,使 model 强而不是 weak,当然要确保, 它在 prepareForReuse: 中正确设置为 nil。这是否有副作用,我还没有意识到?

这是正确的,但如您所知,如果您引用的 model 对象被换出,您将不得不 re-add 观察者。

另一种选择(如果您可以更改 Model class),是在 Model class 中添加一个引用回到您的 self在这种情况下。然后在你的 Model class 的 init/dealloc 中,你可以将自己作为观察者添加到你的新引用中。

最后要注意的是,如果您发现自己发送了一个 addObserver 消息,而接收者 观察者对象是相同的——您也可以覆盖setter 你自己。

在您的示例中,您可以覆盖 -setModel 以执行您要在观察通知处理程序中执行的任何操作 (-observeValueForKey:::)