删除NSOperation的观察者

时间:2011-03-19 09:37:17

标签: iphone objective-c nsoperation

我有一个视图,它通过NSOperationQueue中的NSOperation加载数据。我想允许用户在操作完成之前离开此视图。我的问题是我似乎无法在不崩溃的情况下始终如一地做到这一点。这是我开始操作的代码:

NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release]; 
SportsLoadOperation* loadOperation = [[SportsLoadOperation alloc] init];
[loadOperation addObserver:self forKeyPath:@"isFinished" options:0 context:NULL];
[self.queue addOperation:loadOperation];
[loadOperation release];    

如果我在操作仍在执行时离开视图,我经常会收到此错误:

[SportsViewController retain]: message sent to deallocated instance 0x38b5a0

如果我尝试移除观察者以便不会发生这种情况,请执行以下操作:

-(void)viewWillDisappear:(BOOL)animated {
    if (self.isLoadingData) {
        for (NSOperation *operation in [self.queue operations]) {
            if([operation isExecuting]) {
                [operation cancel];
                [operation removeObserver:self forKeyPath:@"isFinished"];
            }
        }
    }
    [super viewWillDisappear:animated];
}

然后我有时会收到这个错误:

Terminating app due to uncaught exception 'NSRangeException', reason:
'Cannot remove an observer <SportsViewController 0x661c730> for the key path "isFinished" from <SportsLoadOperation 0x66201a0> because it is not registered as an observer.'

如何避免这些问题?

3 个答案:

答案 0 :(得分:3)

第二条错误消息说明了一切。

您是否在removeObserver之后尝试 [operation cancel],看看会发生什么?

您是否尝试过removeObserver,然后才cancel进行操作?

这些可能有助于缩小触发错误的条件。此外,您可能希望将日志输出添加到代码中以查看它实际执行的时间。

而且,就像freespace的回答所说的那样,添加&amp;删除观察者最好在观察实例的构造/销毁方法中完成。这通常会产生更稳定的代码。

答案 1 :(得分:0)

您的SportsLoadOperation实例看起来没有SportsViewController作为观察者。您是否在代码中的其他位置插入了SportsLoadOperation?如果是这种情况,请考虑为initWithObserver编写SportsLoadOperaion方法,以便自动进行观察。这可以避免因忘记在isFinished上设置观察者而导致的错误。

此外,最好在dealloc中删除viewWillDisappear中的观察者,因为在许多情况下会调用viewWillDisappear,例如显示模态视图控制器时。因此,您可能会过早地停止运营。

修改

而不是针对[operation isExecuting]检查[operation isCancelled]。这是因为[operation cancel]没有停止操作 - 如果您实际上没有在isCancelled方法中检查main,它可以并将继续执行。这意味着,如果viewWillDisappear被调用两次,您最终可能会尝试在removeObserver的同一个实例上调用SportsLoadOperation两次,第二次尝试失败。

在以下位置添加一些调试输出语句:

  1. 创建SportsLoadOperation实例并将其插入队列
  2. 当您取消SportsLoadOperation个实例并从中移除观察者时。

答案 2 :(得分:0)

我最终通过覆盖观察到的操作的addObserver和removeObserver方法来解决这个问题,以便跟踪观察者,以便我可以在[cancel]方法中删除它们。

现在我要做的就是在关闭正在观察操作的控制器之前调用操作队列来取消所有操作。

下面,_observers是一个NSMutableDictionary。

- (void)addObserver:(NSObject*)observer
     forKeyPath:(NSString*)keyPath
        options:(NSKeyValueObservingOptions)options context:(void*)context
{
    [super addObserver:observer forKeyPath:keyPath options:options context:context];
    [_observers setValue:observer forKey:keyPath];
}


- (void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath
{
    [super removeObserver:observer forKeyPath:keyPath];
    [_observers removeObjectForKey:keyPath];
}


- (void)cancel
{
    [super cancel];

    for(id key in _observers)
    {
            id object = [_observers valueForKey:key];
            [super removeObserver:object forKeyPath:key];
    }

    [_observers removeAllObjects];

}