通告中心同步地將通告派發給它的觀察者。發出通告的對象直到所有的通告被發出后,才重新獲得程序的控制權。如果需要以異步的方式發送通告,必須使用通告隊列。
NSNotificationCenter
每個任務都有一個缺省的通告中心,您可以通過NSNotificationCenter的defaultCenter類方法來進行訪問。通告中心在單任務中處理通告。如果需要在同一個機器的不同任務之間進行通訊,可以使用分布式通告中心。
通告中心同步地將通告發送給觀察者。換句話說,在發出一個通告時,在所有的觀察者接收和處理完成通告之前,程序的控制權不會返回給發送者。如果需要異步發送通告,可以使用通告隊列,這在"通告隊列"部分中進行描述。
在多線程的應用程序中,通告總是在發送的線程中傳送,這個線程可能不同於觀察者注冊所在的線程。
NSDistributedNotificationCenter
每個任務都有一個缺省的分布式通告中心,您可以通過NSDistributedNotificationCenter的defaultCenter類方法來訪問。這個分布式通告中心負責處理同一個機器的不通任務之間的通告。如果需要實現不同機器上的任務間通訊,請使用分布式對象。
發送分布式通告是一個開銷昂貴的操作。通告會被發送給一個系統級別的服務器,然后再分發到注冊了該分布式通告的對象所在的任務中。發送通告和通告到達另一個任務之間的延遲是很大的。事實上,如果發出的通告太多,以致於充滿了服務器的隊列,就可能被丟棄。
分布式通告通過任務的運行循環來分發。任務必須運行在某種“常見”模式的運行循環下,比如NSDefaultRunLoopMode模式,才能接收分布式通告。如果接收通告的任務是多線程的,則不要以通告會到達主線程作為前提。通告通常被分發到主線程的運行循環上,但是其它線程也可以接收通告。
盡管常規的通告中心允許任何對象作為通告對象(也就是通告封裝的對象),分布式通告中心只支持將NSString對象作為它的通告對象。由於發出通告的對象和通告的觀察者可能位於不同的任務中,通告不能包含指向任意對象的指針。因此,分布式通告中心要求通告使用字符串作為通告對象。通告的匹配就是基於這個字符串進行的,而不是基於對象指針。
通告隊列
NSNotificationQueue對象(或者簡單稱為通告隊列)的作用是充當通告中心(NSNotificationCenter的實例)的緩沖區。通告隊列通常以先進先出(FIFO)的順序維護通告。當一個通告上升到隊列的前面時,隊列就將它發送給通告中心,通告中心隨后將它派發給所有注冊為觀察者的對象。
每個線程都有一個缺省的通告隊列,與任務的缺省通告中心相關聯。圖5-9展示了這種關聯。您可以創建自己的通告隊列,使得每個線程和通告中心有多個隊列。

聚結的通告
NSNotificationQueue類為Foundation Kit的通告機制增加了兩個重要的特性:即通告的聚結和異步發送。聚結是把和剛進入隊列的通告相類似的其它通告從隊列中移除的過程。如果一個新的通告和已經在隊列中的通告相類似,則新的通告不進入隊列,而所有類似的通告(除了隊列中的第一個通告以外)都被移除。然而,您不應該依賴於這個特殊的聚結行為。
您可以為enqueueNotification:postingStyle:coalesceMask:forModes:方法的第三個參數指定如下的一或多個常量,指示簡化的條件:
-
NSNotificationNoCoalescing -
NSNotificationCoalescingOnName -
NSNotificationCoalescingOnSender
您可以對NSNotificationCoalescingOnName和NSNotificationCoalescingOnSender常量進行位或操作,指示Cocoa同時使用通告名稱和通告對象進行聚結。那樣的話,和剛剛進入隊列的通告具有相同名稱和發送者對象的所有通告都會被聚結。
異步發送通告
通過NSNotificationCenter類的postNotification:方法及其變體,您可以將通告立即發送給通告中心。但是,這個方法的調用是同步的:即在通告發送對象可以繼續執行其所在線程的工作之前,必須等待通告中心將通告派發給所有的觀察者並將控制權返回。但是,您也可以通過NSNotificationQueue的enqueueNotification:postingStyle:和enqueueNotification:postingStyle:coalesceMask:forModes:方法將通告放入隊列,實現異步發送,在把通告放入隊列之后,這些方法會立即將控制權返回給調用對象。
Cocoa根據排隊方法中指定的發送風格和運行循環模式來清空通告隊列和發送通告。模式參數指定在什么運行循環模式下清空隊列。舉例來說,如果您指定NSModalPanelRunLoopMode模式,則通告只有當運行循環處於該模式下才會被發送。如果當前運行循環不在該模式下,通告就需要等待,直到下次運行循環進入該模式。
向通告隊列發送通告可以有三種風格:NSPostASAP、NSPostWhenIdle、和NSPostNow,這些風格將在接下來的部分中進行說明。
盡快發送
以NSPostASAP風格進入隊列的通告會在運行循環的當前迭代完成時被發送給通告中心,如果當前運行循環模式和請求的模式相匹配的話(如果請求的模式和當前模式不同,則通告在進入請求的模式時被發出)。由於運行循環在每個迭代過程中可能進行多個調用分支(callout),所以在當前調用分支退出及控制權返回運行循環時,通告可能被分發,也可能不被分發。其它的調用分支可能先發生,比如定時器或由其它源觸發了事件,或者其它異步的通告被分發了。
您通常可以將NSPostASAP風格用於開銷昂貴的資源,比如顯示服務器。如果在運行循環的一個調用分支過程中有很多客戶代碼在窗口緩沖區中進行描畫,在每次描畫之后將緩沖區的內容刷新到顯示服務器的開銷是很昂貴的。在這種情況下,每個draw...方法都會將諸如“FlushTheServer” 這樣的通告排入隊列,並指定按名稱和對象進行聚結,以及使用NSPostASAP風格。結果,在運行循環的最后,那些通告中只有一個被派發,而窗口緩沖區也只被刷新一次。
空閑時發送
以NSPostWhenIdle風格進入隊列的通告只在運行循環處於等待狀態時才被發出。在這種狀態下,運行循環的輸入通道中沒有任何事件,包括定時器和異步事件。以NSPostWhenIdle風格進入隊列的一個典型的例子是當用戶鍵入文本、而程序的其它地方需要顯示文本字節長度的時候。在用戶輸入每一個字符后都對文本輸入框的尺寸進行更新的開銷是很大的(而且不是特別有用),特別是當用戶快速輸入的時候。在這種情況下,Cocoa會在每個字符鍵入之后,將諸如“ChangeTheDisplayedSize”這樣的通告進行排隊,同時把聚結開關打開,並使用NSPostWhenIdle風格。當用戶停止輸入的時候,隊列中只有一個“ChangeTheDisplayedSize”通告(由於聚結的原因)會在運行循環進入等待狀態時被發出,顯示部分也因此被刷新。請注意,運行循環即將退出(當所有的輸入通道都過時的時候,會發生這種情況)時並不處於等待狀態,因此也不會發出通告。
立即發送
以NSPostNow風格進入隊列的通告會在聚結之后,立即發送到通告中心。您可以在不需要異步調用行為的時候 使用NSPostNow風格(或者通過NSNotificationCenter的postNotification:方法來發送)。在很多編程環境下,我們不僅允許同步的行為,而且希望使用這種行為:即您希望通告中心在通告派發之后返回,以便確定觀察者對象收到通告並進行了處理。當然,當您希望通過聚結移除隊列中類似的通告時,應該用enqueueNotification...方法,且使用NSPostNow風格,而不是使用postNotification:方法。
