3

我有一些适用于许多对象的代码,将我的类注册为 KVO:

for (SPPanelManager *manager in self.panelManagers) {
    [manager addObserver:self forKeyPath:@"dataFetchComplete" options:0 context:NULL];
    [manager fetchData];
}

然后,当它观察到发生在每个这些对象上的变化时,我取消注册:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
        [object removeObserver:self forKeyPath:@"dataFetchComplete"];

        //Other stuff
    }
}

然后,当我稍后离开 UIViewController 时,每个管理器对象都会出现以下错误:

一个类的实例被解除分配,而键值观察者仍向其注册。观察信息被泄露,甚至可能被错误地附加到其他对象上。

我不知道为什么它给了我这个错误 - 这些是 KVO 唯一被引用的两个地方,所以它不是另一个观察者。

4

3 回答 3

2

您的类(观察者)在某些活动中被释放。您必须在它被释放或不被进一步使用之前取消注册它。viewDidUnload:在或中使用下面的代码dealloc:

for (SPPanelManager *manager in self.panelManagers) {
    [manager removeObserver:self forKeyPath:@"dataFetchComplete" context:NULL];
  }
于 2013-07-08T12:06:30.220 回答
0

不要尝试在observeValueForKeyPath:ofObject:change:context:. KVO 期望给定 Tuple( object, keyPath, context) 的观察者列表在该组合的通知中保持不变。即使它“有时”起作用,行为也是不确定的,因为不能保证通知观察者的顺序(它可能在内部使用集合类型的数据结构。)

解决此问题的最简单方法可能类似于:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
        CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
            [object removeObserver:self forKeyPath:@"dataFetchComplete"];
        });
    }
}

这将导致在运行循环中的下一个可能点删除观察(除非自定义运行循环模式,这可能会稍微延迟其执行,但通常不会有问题)。此外,您不必担心self或被object释放,因为它们将被块关闭保留,直到块执行并且本身释放。

正如您所发现的,KVO 对于一次性通知来说并不是一个特别好的 API。

至于最初的错误消息,您需要删除观察。您可能可以在观察对象中做到这一点,dealloc但您应该真正避免在其中进行“真正的工作”,dealloc并且删除观察可以说是“真正的工作”。不幸的是,Cocoa 中没有标准的拆卸模式,所以你必须自己触发拆卸,也许当你从视图控制器中分离出来时,或者类似的事情。建议的另一个答案viewDidUnload,但在 iOS 6 中已弃用,并且永远不会被调用,因此它不再是一个好方法。

于 2013-07-08T11:55:41.027 回答
0

您必须简单地释放您的GMS_MapView对象并删除 MapView Observer forkeypath。

(void)viewWillDisappear:(BOOL)animated{

    [super viewWillDisappear:animated];

    [objGMS_MapView removeObserver:self forKeyPath:@"myLocation"];

    //=>    Set map delegate to nil (to avoid:   mapView:regionDidChangeAnimated:]: message sent to deallocated instance )
    objGMS_MapView.delegate = nil;
    objGMS_MapView = nil;
}
于 2015-09-17T12:16:19.873 回答