iOS - 如何从单身NSObject中删除KVO的观察者?

时间:2013-03-15 04:33:37

标签: ios singleton key-value-observing nsobject observers

我有一个NSObject的共享单例类,我运行了一些操作队列。我遇到了崩溃:

[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

似乎我需要使用'removeObserver:'来防止这种情况发生,但我如何在共享对象上正确地执行此操作?

CODE:

-(void)synchronizeToDevice{
    queue = [NSOperationQueue new];
    queue.name = @"SynchronizeToDeviceQueue";
    //Sync Active User
    NSInvocationOperation *operationUser = [[NSInvocationOperation alloc] initWithTarget:self
                                                                                selector:@selector(downloadUserData:)
                                                                              object:[self activeUserID]];

    [queue addOperation:operationUser];

    //Sync Video Data
    NSInvocationOperation *operationVideos = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(downloadVideoData)
                                                                              object:nil];
    [queue addOperation:operationVideos];


    [queue addObserver:self forKeyPath:@"operations" options:0 context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == queue && [keyPath isEqualToString:@"operations"]) {
        //Synchronization Queue
        if ([queue.name isEqualToString:@"SynchronizeToDeviceQueue"] && [queue.operations count] == 0) {
            //Queue Completed
            //Notify View Synchronization Completed
            [self performSelectorOnMainThread:@selector(postNotificationDidFinishSynchronizationToDevice) withObject:nil waitUntilDone:NO];
        }
        //Video Download Queue
        if ([queue.name isEqualToString:@"VideoFileDownloadQueue"] && [queue.operations count] == 0) {
            //Notify View Video File Download Completed
            [self performSelectorOnMainThread:@selector(postNotificationDidFinishDownloadingVideo) withObject:nil waitUntilDone:NO];
        }
        //Active User Sync Queue
        if ([queue.name isEqualToString:@"SynchronizeActiveUserToDeviceQueue"] && [queue.operations count] == 0) {
            //Queue Completed
            //Notify View Synchronization Completed
            [self performSelectorOnMainThread:@selector(postNotificationDidFinishActiveUserSynchronizationToDevice) withObject:nil waitUntilDone:NO];
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

CRASH LOG:

2013-03-14 21:48:42.167 COMPANY[1946:1103] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<DataManager: 0x1c54a420>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: operations
Observed object: <NSOperationQueue: 0x1c5d3360>{name = 'SynchronizeActiveUserToDeviceQueue'}
Change: {
    kind = 1;
}
Context: 0x0'
*** First throw call stack:
(0x336262a3 0x3b4b197f 0x336261c5 0x33f1a56d 0x21bd1 0x33eb46b9 0x33eb4313 0x33eb3a25 0x33eb3817 0x33f2b689 0x3b8ccb97 0x3b8cf139 0x3b8cd91d 0x3b8cdac1 0x3b8fda11 0x3b8fd8a4)
libc++abi.dylib: terminate called throwing an exception

2 个答案:

答案 0 :(得分:2)

在“Key-Value Observing Programming Guide”的Receiving Notification of a Change中,给出了observeValueForKeyPath的示例实现,其中包含注释:

  

确保调用超类的实现,如果它实现它。   NSObject没有实现该方法。

您说您的班级是NSObject的子类,因此您不应该致电[super observeValueForKeyPath:...]

如果在同一个共享实例上多次调用synchronizeToDevice,则会出现另一个问题。在这种情况下,您创建一个新的queue并注册一个观察者。但是旧队列的观察者没有被删除。

因此,可能会为“旧队列”和检查调用observeValueForKeyPath if (object == queue)失败,导致不必要的超级电话。

因此,如果可以多次调用synchronizeToDevice,则应首先删除旧的观察者。

答案 1 :(得分:2)

我怀疑您拨打synchronizeToDevice的电话不止一次。如果是这样,您将继续观察旧队列以及一些新队列。当observeValueForKeyPath:...触发时,它可能会传递给你的旧队列,然后你忽略它,调用super,因为你没有处理你要求的观察而抛出异常。

这里你真正的问题是你没有使用访问器。那会更加清晰。例如,这是您实现setQueue:

的方式
-(void)setQueue:(NSOperationQueue *)queue {
  if (_queue) {
    [_queue removeObserver:self forKeyPath:@"operations"];
  }

  _queue = queue;

  if (_queue) {
    [_queue addObserver:self forKeyPath:@"operations" options:0 context:NULL];
  }
}

现在,当您致电self.queue = [NSOperationQueue new];时,一切都会自动生效。您停止观察旧队列并开始观察新队列。如果您致电self.queue = nil,它会自动取消注册。

您仍然需要确保在dealloc中取消注册:

- (void)dealloc {
  if (_queue) {
    [_queue removeObserver:self forKeyPath:@"operations"];
  }
}