[iOS] Notification Programming Topics --- Notification Queues

此份文件為個人學習通知中心的翻譯,內文請參考蘋果官方文件,若有翻譯錯誤請見諒,請勿挪作商業用途,並著明出處。

 原始文章連結:
http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Notifications/

NSNotificationQueue物件,或簡單說,通知隊列(notification queues)作為通知中心(NSNotificationCenter的實例)的緩衝區。NSNotificationQueue類別對於Foundation Kit的通知機制而言,有兩個重要的特色:通知的聚合(the coalescing of notification)和非同步貼出(asynchronous posting)。


Notification Queue Basics

使用NSNotificationCenter的postNotification:方法和它的變數,你可以貼出一個通知到通知中心。然而,此方法的調用是同步的:在張貼物件(posting object)可以啓動它的執行線程前,它必須等到通知中心派送通知道所有的物件並且返回。另一方面,一般來說通知對列以先進先出(First In First Out, FIFO)的順序保存通知(NSNotification的實例),當一個通知被提升到隊列的最前面時,隊列將它貼到通知中心去,通知中心會把這通知派送到所有註冊為觀察者的物件。

每一個線程都有預設的通知隊列,隊列與該程序(process)預設的通知中心有關。你可以創建你自己的通知隊列並且在每個中心和線程上有多重的隊列。

Posting Notifications Asynchronously

使用NSNotificationQueue的enqueueNotification:postingStyle:和enqueueNotification:postingStyle:coalesceMask:forModes:方法,你可以把通知放到隊列中,非同步的貼出通知到目前的線程。放入通知到隊列後,這些方法會立刻返回到調用物件(invoking object)去。
 
注意:當排列通知的線程在通知對列貼出通知到通知中心前就終結時,通知就不會被貼出。參考“Delivering Notifications To Particular Threads”以瞭解如何貼出通知到不同的線程。

通知隊列被清除和它的通知張貼是基於張貼風格(posting style)以及在enqueuing方法中指明的run loop mode。mode引數指明run loop mode,在該run loop mode下隊列將被清除。舉例來說,若你指明NSModalPanelRunLoopMode,通知只有當run loop 是這種mode時才會被貼出。若目前的run loop 不是這種mode,通知會等到下一次那個模式進入時。參考Threading Programming Guide裡面的“Run Loop Modes”。

張貼到通知對列可以發生在下列三種不同的風格中:NSPostASAP、NSPostWhenIdle以及 NSPostNow。下面章節將要介紹這三種風格。

Posting As Soon As Possible



假設目前的run loop匹配要求的mode,當目前的run loop的遞迴完成時,任何有NSPostASAP風格的通知隊列會張貼到通知中心(若要求的mode和目前的不同,通知會等到要求的mode進入後才張貼)。因為在每一次的遞迴中,run loop可以做多重的callout,目前的callout存在並且控制返回到run loop時通知就能或不能被傳遞。其他callout可能會先發生,像是計時器(timer)或是source firing或是其他被傳遞的非同步通知。

最典型的,是對expensive resource使用NSPostASAP張貼風格,像是display server。當run loop裡面於callout期間,許多在視窗上的client draw緩衝,在每次draw operation後去刷新(flush)display server的緩衝很耗能。在這情況下,每一個draw...方法會排列一些通知,像是FlushTheServer,有聚合指定的名稱和物件,以及NSPostASAP的張貼風格。因此,那些通知裡面只有一個會在run loop結束時被派送,而且視窗緩衝(window buffer)只會被刷新(flush)一次。

Posting When Idle

只有當run loop處於等待狀態時, 以NSPostWhenIdle風格的排隊的通知會被貼出。
In this state, there’s nothing in the run loop’s input channels, be it timers or other asynchronous events.(完全被這句打敗...)。


以NSPostWhenIdle風格來列隊的典型例子是,使用者輸入文字,並且程式在某處以字節(bytes)顯示文字的大小。在每一次使用者輸入字符時更新text field size是很耗能(又不是很有用)的事,特別是如果使用者輸入的很快時。在這情況下,程式會排列ChangeTheDisplayedSize通知,是啟動聚合並且在每個字符輸入後張貼風格是NSPostWhenIdle的通知。當使用者停止輸入,(根據聚合)在隊伍中單一的ChangeTheDisplayedSize通知會在run loop進入等待狀態並且顯示被更新時貼出。注意即將要跳出(exit)的run loop(當有所有的input channel失效時會發生)不算是等待狀態,因此不會貼出通知。

Posting Immediately

在聚合到通知中心後,以NSPostNow列隊的通知會立刻被貼出。當你不要求非同步呼叫的行為時,你可將NSPostNow風格的通知列隊(若是以postNotification:來貼出)。對很多程式化的情況是,同步行為不只是可允許的也是想要的:你想要通知中心在派送後返回,讓你可以確保觀察物件有接收到並且處理通知。當然,如果隊列中有你想要以聚合來移除的相似的通知,你應該使用enqueueNotification...加上NSPostNow,而不是使用postNotification:。

(這三段有夠難搞,我來大致歸納一下)

                  張貼時機
NSPostNow          立刻
NSPostWhenIdle   run loop等待時
NSPostASAP       run loop完成時

Coalescing Notifications

某些情況下,如果一個事件至少會發生一次,你或許想要貼出通知,但即使這事件發生很多次,你只希望貼出一次。舉例來說,在一個應用程式中,接收在分散的封包中的資料,然後依據封包的收據(receipt),你希望貼出一個通知來指出需要處理的資料,此外,貼出這些通知的物件可能無法知道是否有更多封包過來,以及是否貼出的方法是以迴圈來呼叫。
在某些情況可能可以簡單的設定一個布林標誌(Boolean flag)來表示事件已發生並且等待標誌清除後才貼出更多通知。然而,如果不可能的話,你不能直接使用NSNotificationCenter,因為它的行為是同步的,通知會於返回前被貼出,因此無法忽略重複的通知,此外,NSNotificationCenter實例沒辦法知道是否有更多的通知過來。

因此,你應該把通知加到NSNotificationQueue實例去並指定適當的coalescing操作,而不是把通知貼到通知中心去。Coalescing是一個從queue中移除那些以某種形式上和較早於隊伍中的通知所相似的通知之過程。藉著在方法enqueueNotification:postingStyle:coalesceMask:forModes:中的第三個引數指明以下的一個或一個以上的常數,來指出相似的情況。

NSNotificationNoCoalescing         不要聚合在隊伍中的通知
NSNotificationCoalescingOnName    聚合有相同名字的通知
NSNotificationCoalescingOnSender   聚合有相同物件的通知

你可以在NSNotificationCoalescingOnName和NSNotificationCoalescingOnSender常數上執行bitwise-OR操作使用通知名稱和通知物件兩者來指明coalescing以下的力子展示了你如何使用queue來確保在一個給定的事件迴圈循環中所有名為MyNotificationName的通知被聚合成單一的通知。

// MyNotificationName defined globally
NSString *MyNotificationName = @"MyNotification";
id object = <#The object associated with the notification#>;
NSNotification *myNotification =
[NSNotification notificationWithName:MyNotificationName object:object]
[[NSNotificationQueue defaultQueue]
enqueueNotification:myNotification
postingStyle:NSPostWhenIdle
coalesceMask:NSNotificationCoalescingOnName
forModes:nil];

留言