2011-03-19 117 views
1

我有一个视图,它通过NSOperationQueue中的NSOperation加载数据。我希望允许用户在操作完成之前离开此视图。我的问题是,我似乎无法一直这样做,而不会崩溃。这里是我的代码开始操作:如果我尝试删除观察员,以便删除NSOperation的观察者

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

:如果我离开,而操作仍在进行视图

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];  

,我经常会收到这样的错误这种情况不会发生,就像这样:

-(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

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

您是否试过不是removeObserver[operation cancel]后面看看会发生什么呢?

您是否试过首先removeObserver并且只有cancel的操作?

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

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

+0

订单没有区别。我认为我误解了freespaces的原始答案 - 我会尝试添加和删除实际构造/销毁方法中的观察者。这听起来更聪明。是否可以在dealloc中引用self,如下所示:'[self removeobserver:self.observer forKeyPath:@“isFinished”]; [观察员发布],观察员=零;'? – 2011-03-19 11:08:58

+0

这是我的解决方案:添加和删除观察者最好在观察到的实例的构建/销毁方法中完成 – 2011-03-27 06:56:07

+0

尽管如此,该如何工作?该操作将尝试通知解除分配操作之前的释放观察者。 – Oscar 2012-01-16 02:47:15

0

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

另外,在dealloc然后在viewWillDisappear中删除观察者可能更好,因为viewWillDisappear在许多情况下被调用,例如,显示模态视图控制器时。因此你可能会过早地停止你的操作。

编辑

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

添加一些调试输出语句在以下地方:

  1. 当你创建一个SportsLoadOperation实例,并将其插入到queueu
  2. 当你取消SportsLoadOperation实例,并从中取出的观察员。
+0

恐怕不行,加载操作从一个特定的“重载”函数调用,它不会出现在其他地方。关于过早停止操作的公平点,但并不能帮助我目前的问题。谢谢你给它一个镜头。 – 2011-03-19 10:07:49

+0

当'cancel'发送到你的'SportsLoadOperation'时,你是否真的停止了?如果没有,'removeObserver ...'代码完全可以在相同的'SportsLoadOperation'实例上执行两次,因为只调用'cancel' * * *提醒*操作停止,不会使其停止执行。 – freespace 2011-03-19 10:33:53

+0

由于操作只是运行同步下载,所以当我调用cancel时,我目前没有做任何事情。我可能会打断这件事,但目前我还没有。你能解释一下'removeObserver'代码可以执行两次吗?也许当'NSOperationQueue'被释放? – 2011-03-19 10:53:36

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]; 

}