2013-03-15 104 views
0

我有一个共享的单例类的NSObject的,我在运行某些操作队列我得到这个崩溃:iOS - 如何从singleton NSObject中为KVO移除观察者? 。

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

崩溃日志:

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 
+0

你能后的崩溃日志 – 2013-03-15 04:44:02

+0

什么代码,在什么类,崩溃?是什么让你相信去除观察者会阻止它?不幸的是你的问题现在还不清楚。 – 2013-03-15 04:48:25

+0

对不起,添加了代码和崩溃日志。谢谢您的帮助! – JimmyJammed 2013-03-15 04:50:21

回答

2

在 “键 - 值观察编程指南” 的observeValueForKeyPath示例实现的Receiving Notification of a Change给出,用评论:

如果它实现,请务必调用超类的实现。 NSObject不执行该方法。

你说你的班级是NSObject的一个子班,因此你不应该打电话给[super observeValueForKeyPath:...]。如果你打电话超过一次synchronizeToDevice更在同一个共享实例会发生

另一个问题。在这种情况下,您将创建一个新的queue并为此注册一个观察者。但旧队列的观察者不会被删除。

因此,observeValueForKeyPath可能被调用为“旧队列”,并且检查 if (object == queue)失败,导致不需要的超级调用。

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

+1

虽然真实有用,但我不认为这是真正的问题。错误告诉我们的是,当没有人期待它时(没有人处理它),KVO回调被调用。只要删除对“super”的调用就可以掩盖这个问题,但问题依然存在。对'super'的调用本质上就像是一个'NSAssert(I_SHOULD_HAVE_HANDLED_THAT)'。 – 2013-03-20 21:58:58

+0

@RobNapier:是的,这听起来很合理。 – 2013-03-20 22:03:36

+0

@RobNapier:我没有注意到你在更新我的时候发布了一个答案。 – 2013-03-20 22:11:32

1

我怀疑你对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"]; 
    } 
} 
+0

所以你是正确的,每次打开popover它调用synchronizeToDevice。所以弹出窗口的快速打开/关闭最终导致崩溃。但是,我添加了您提供的自定义setQueue方法,但它仍然崩溃。我设置了断点以确保它被调用,并且它不知道它为什么仍然崩溃。有什么建议么? – JimmyJammed 2013-03-21 03:41:51