0

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

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

因为我有一个用于长任务的过期处理程序,所以我在 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 的观察者删除了。我是否正确删除了 self?谢谢!

4

1 回答 1

0

你确定你只打-addObserver:...一次?如果您-addObserver:...多次调用,您将收到多个回调,-observeValueForKeyPath:...并且您必须-removeObserver:为每次调用调用一次-addObserver:...才能停止接收回调。

我在这里也看到了另一个问题;以我的经验,调用addObserver:...or removeObserver:...in observeValueForKeyPath:...(对于相同的 keyPath)是一个麻烦的秘诀。您尚未发布带有“确凿证据”堆栈框架的崩溃跟踪,但是在通知期间添加和删除相同关键路径的观察者可能会导致间歇性崩溃。我凭经验观察到,通知给定属性的观察者的顺序是不确定的,因此即使在通知处理程序中添加或删除观察者有时有效,它也可能在不同情况下任意失败。(我假设在 KVO 的内部某个地方有一个无序的数据结构,它可以以不同的顺序枚举,可能基于指针的数值或类似的任意值。)

您可以使用 GCD 或-performSelector:withObject:afterDelay:在 runloop 的当前迭代完成后删除您的观察。但是,这并不能保证您不会再收到任何通知,因此如果您需要该保证,则必须针对这种情况构建一个侦听器状态检查。

最后,请为所有 KVO 遵守使用上下文。有关详细信息,请参阅我在此处发布的说明。

于 2013-08-03T19:34:03.667 回答