从自我中删除KVO

时间:2012-08-06 20:11:39

标签: iphone key-value-observing

我正在为我的NSOperationQueue注册回调,如下所示:

[self.queue addObserver:self forKeyPath:@"operationCount" options:NSKeyValueObservingOptionNew context:NULL];

因为我有长期任务的expirationhandler,所以我在operationCount的回调中执行此操作。我基本上是在队列中的NSOperation结束后尝试保存状态,然后再恢复。所以我这样做:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"operationCount"]) {
        NSNumber *num = [change objectForKey:NSKeyValueChangeNewKey];
        self.progress = (1.0 - (double)[num integerValue] / self.totalPackets);

        if ([UIApplication sharedApplication].backgroundTimeRemaining <= MIN_BACKGROUND_TIME) {
            // Background time is almost up, save the state and resume later
            NSLog(@"running out of time");
            [self.queue cancelAllOperations];
            [self.queue removeObserver:self forKeyPath:@"operationCount" context:NULL];

            if (self.patientProcessingTaskID != UIBackgroundTaskInvalid) {
                [[UIApplication sharedApplication] endBackgroundTask:self.patientProcessingTaskID];
                self.patientProcessingTaskID = UIBackgroundTaskInvalid;
            }
        }

        if (self.queue.operationCount == 0) {
            NSLog(@"no more operations");
            [self.queue removeObserver:self forKeyPath:@"operationCount" context:NULL];

            if (self.patientProcessingTaskID != UIBackgroundTaskInvalid) {
                [[UIApplication sharedApplication] endBackgroundTask:self.patientProcessingTaskID];
                self.patientProcessingTaskID = UIBackgroundTaskInvalid;
            }
        }
    }
}

它无法正常工作。我逐步完成代码,我看到[self.queue removeObserver:..]运行。但是,我仍然在我的obserValueForKeyPath:方法中得到一个回调,我不知道为什么(假设我将自己移除为self.queue的观察者。我是否正确地删除了自己?谢谢!

1 个答案:

答案 0 :(得分:0)

您确定只打算-addObserver:...一次吗?如果您多次致电-addObserver:...,您将获得-observeValueForKeyPath:...的多次回调,每次拨打-removeObserver:时,您都必须致电-addObserver:...一次,以便停止接收回调。

我也看到了另一个问题;根据我的经验,在addObserver:...中调用removeObserver:...observeValueForKeyPath:...(对于相同的keyPath)是一个麻烦的方法。您没有在其中发布带有“冒烟枪”堆栈帧的崩溃痕迹,但在通知期间为相同的密钥路径添加和删除观察者可能会导致间歇性崩溃。我根据经验观察到通知给定属性的观察者的顺序是非确定性的,因此即使在通知处理程序中添加或删除观察者在某些时候工作,它也可能在不同情况下任意失败。 (我假设在KVO的某个地方有一个无序的数据结构可以在不同的顺序中枚举,可能基于指针的数值或类似的任意东西。)

在runloop的当前迭代完成后,您可以使用GCD或-performSelector:withObject:afterDelay:删除您的观察。这并不保证您不会再收到任何通知,因此如果您需要这种保证,您必须检查监听器的状态是否存在。

最后,请为所有KVO纪念活动使用上下文。有关详细信息,请参阅我在here上发布的说明。