2013-03-15 72 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