2012-12-06 81 views
8

即使在方法調用正在進行時( link *,對象可能會被釋放,對於註冊並接收將在不同線程上傳遞的通知的安全從它預期將被釋放的那個?NSNotificationCenter和安全多線程

作爲參考,documentation指出

在多線程應用程序,通知始終在該通知被張貼線程,這可能不是由觀察者本身註冊在同一個線程交付。

同樣重要的是,NSNotificationCenter不保留對已註冊接收通知的對象的強引用。

下面是一個例子,這可能使局勢更加具體:

- (id)init { 
    self = [super init]; 
    if (self) { 
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:SomeNotification object:nil]; 
    } 
} 

- (void)dealloc { 
    [[NSNotificationCenter defaultCenter] removeObserver:self]; 
} 

- (void)handleNotification:(NSNotification *)notification { 
    // do something 
} 

與此實施的對象上接收線程X. SomeNotification之前-handleNotification:回報,最後強大的參考對象(代碼我可以看到)壞了。

  1. 我是在想,糾正:

    一個。如果NSNotificationCenter在調用-handleNotification之前對該對象進行強引用,那麼該對象將不會被釋放,直到after -handleNotification:返回,並且

    b。就可以了,那麼對象可-handleNotification之前釋放:如果NSNotificationCenter不調用-handleNotification之前採取強有力的參考對象返回

  2. 哪種方式(A或B),它的工作原理?我還沒有在文檔中發現該主題,但在多線程環境中安全使用NSNotificationCenter似乎有點重要。

UPDATE:在上述環節的答案被更新表示「ARC保留並圍繞弱引用調用釋放」。這意味着在方法調用正在進行時不應該釋放對象。

回答

8

我總是建議,如果您看到通知在主線程以外的線程上飛來飛去,並且您看到在後臺發生釋放,則線程可能太複雜。 ObjC不是一種線程快樂的語言。大多數線程工作應該以隊列中短暫的塊的形式存在。他們可以將通知輕鬆發佈回主線程,但不應該經常使用通知。

也就是說,今天管理多線程通知的最佳方式是addObserverForName:object:queue:usingBlock:。這使您可以更好地控制生命週期。模式應該看起來像這樣:

__weak id weakself = self; 
id notificationObserver = [[NSNotificationCenter defaultCenter] 
addObserverForName:... 
object:... 
queue:[NSOperationQueue mainQueue] 
usingBlock:^(NSNotification *note){ 
    id strongself = weakself; 
    if (strongself) { 
    [strongself handleNotification:note]; 
    } 
}]; 

注意使用weakself/strongself。我們正在避免使用weakself的保留循環。當我們回來時,我們首先強烈地參考。如果我們仍然存在,那麼我們鎖定其餘的塊(所以我們不能在handleNotification:中重新分配)。如果我們不存在,那麼通知將被丟棄。 (請注意,在調用dealloc之前,弱引用實際上爲零,請參見objc_loadWeak。)我在此處使用mainQueue,但您當然可以使用另一個隊列。

在「過去的日子」(10.6以前)中,我通過控制對象的生命週期來設計這個問題。基本上,我設計的這樣短命的對象不會收聽可能來自其他線程的通知。這比聽起來容易得多,因爲在10.6之前的代碼中,線程可以保持很少(並且IMO仍然應該保持在較低的水平)。 NSNotificationCenter是專爲低線程世界設計的。

另請注意,與-[NSNotificationCenter addObserver:selector:name:object:]不同,-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]會返回一個作爲觀察者的不透明對象。您必須跟蹤此對象,以便稍後可以移除觀察者。

+0

感謝您的回答。這種方法確實解決了導致我的問題的問題。我仍想知道,在我提出的例子中,NSNotificationCenter在調用它註冊的方法時保留了觀察者。 –

+0

我沒有看到任何承諾的文檔。你可以嘗試通過使dealloc非常慢(例如使用sleep())並添加printf()日誌來探索它。 (絕對不要使用NSLog進行線程分析。)但是我懷疑你可以依賴於結果。即使ARC承諾它,你也不知道NSNotificationCenter是否在內部使用ARC。唯一真正的承諾是你從文檔中獲得的。 –

+0

感謝您的編輯;我已經在真正的代碼中搞砸了...... –

0

NSNotificationCenter不強烈地引用該對象,因此必須在釋放之前移除觀察者。當啓用ARC時,如果調用handleNotification,則由於調用handleNotification將增加其保留計數,因此觀察者將不會被釋放。如果觀察者在發佈通知之前被釋放,那麼當您在dealloc方法中寫入時,NSNotificationCenter將從觀察者中刪除它,以便不會調用handleNotification。 NSNotificationCenter在發佈通知時同步調用通知處理程序。