0

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

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

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

代码:

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

崩溃日志:

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
4

2 回答 2

2

接收“Key-Value Observing Programming Guide”变更通知中,给出了一个示例实现observeValueForKeyPath,并附有注释:

如果它实现了它,请务必调用超类的实现。NSObject 没有实现该方法。

你说你的类是的子类NSObject,所以你不应该调用[super observeValueForKeyPath:...]

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

因此,observeValueForKeyPath可能会调用“旧队列”并且检查 if (object == queue)失败,从而导致对 super 的不必要调用。

所以如果 synchronizeToDevice可以被多次调用,你应该先移除旧的观察者。

于 2013-03-20T21:47:30.643 回答
2

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

您真正的问题是您没有使用访问器。这会让事情变得更清楚。例如,这就是你将如何实现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"];
  }
}
于 2013-03-20T22:06:21.877 回答