删除 NSOperation 的观察者

发布于 2024-10-22 22:00:39 字数 1347 浏览 2 评论 0原文

我有一个通过 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.'

如何避免这些问题?

I have a view which loads data via an NSOperation within an NSOperationQueue. I want to allow users to leave this view before the operation has completed. My problem is that I can't seem to consistently do this without crashing. Here is my code to start the operation:

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

If I leave the view while the operation is still executing, I often get this error:

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

If I try to remove the observers so that this doesn't occur, like this:

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

Then I sometimes get this error:

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.'

How can I avoid these problems?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

听你说爱我 2024-10-29 22:00:39

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

您是否尝试过在[操作取消]之后removeObserver并看看会发生什么?

您是否尝试过先removeObserver,然后才取消操作?

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

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

The 2nd error message says it all.

Have you tried to not removeObserver after [operation cancel] and see what happens then?

Have you tried to first removeObserver and only then cancel the operation?

These might help to narrow down the conditions that trigger the error. Also, you might want to add log output to the code to see when it actually executes.

And, like freespace's answer says, adding & removing observers is best done in the construction / destruction methods of the observed instances. This generally yields more stable code.

左岸枫 2024-10-29 22:00:39

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

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

编辑

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

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

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

Looks like you have an instance of SportsLoadOperation that doesn't have SportsViewController as an observer. Are you inserting SportsLoadOperation anywhere else in your code? If this is the case, consider writing an initWithObserver method for SportsLoadOperaion that will do the observing automatically. This avoids errors caused by forgetting to set the observer on isFinished.

Also, it is probably better to do the removal of observer in dealloc then in viewWillDisappear because viewWillDisappear is called in many circumstances, e.g. when displaying a modal view controller. Thus you might be prematurely stopping your operations.

Edit

Instead of checking against [operation isExecuting] check against [operation isCancelled]. This is because [operation cancel] doesn't stop an operation - it can and will continue executing if you don't actually check for isCancelled in your main method. This means that if viewWillDisappear is called twice, you could end up attempting to call removeObserver twice on the same instance of SportsLoadOperation, with the second attempt failing.

Add some debugging output statements in the following places:

  1. when you create a SportsLoadOperation instance and insert it into the queueu
  2. when you are cancelling a SportsLoadOperation instance and removing from it observers.
久而酒知 2024-10-29 22:00:39

我最终通过重写观察操作的 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];

}

I ended up solving this by overriding the observed operation's addObserver and removeObserver methods, to keep track of observers so I could remove them in the [cancel] method.

All I have to do now is call the operation queue to cancel all operations before I dismiss the controller that was observing the operations.

Below, _observers is an 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];

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文