若是在一個類中想要執行另外一個類中的方法可使用通知
1.建立一個通知對象:使用notificationWithName:object: 或者 notificationWithName:object:userInfo:
html
NSNotification* notification = [NSNotification notificationWithName:kImageNotificationLoadFailed(connection.imageURL)
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error,@"error",connection.imageURL,@"imageURL",nil]];編程
這 裏須要注意的是,建立本身的通知並非必須的。而是在建立本身的通知以前,採用NSNotificationCenter類的方 法 postNotificationName:object: 和 postNotificationName:object:userInfo:更加便利的發出通知。這種狀況,通常使用NSNotificationCenter的類方法defaultCenter就得到默認的通知對象,這樣你就能夠給該程序的默認通知中心發送通知了。注意:每個程序都有一個本身的通知中心,即NSNotificationCenter對象。該對象採用單例設計模式,採用defaultCenter方法就能夠得到惟一的NSNotificationCenter對象。設計模式
注意:NSNotification對象是不可變的,由於一旦建立,對象是不能更改的。服務器
2.註冊通知:addObserver:selector:name:object:網絡
能夠看到除了添加觀察者以外,還有其接收到通知以後的執行方法入口,即selector的實參。所以爲了進行防護式編程,最好先檢查觀察者是否認義了該方法。例如:添加觀察者代碼有多線程
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(aWindowBecameMain:)
name:NSWindowDidBecomeMainNotification object:nil];
app
這裏保證了self定義了aWindowBecameMain:方法。而對於一個任意的觀察者observer,不能保證其對應的selector有aWindowBecameMain:,可採用[observer respondsToSelector:@selector(aWindowBecameMain:)]] 進行檢查。因此完整的添加觀察者過程爲:框架
if([observer respondsToSelector:@selector(aWindowBecameMain:)]) {
[[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil];
}
異步
注 意到addObserver:selector:name:object:不只指定一個觀察者,指定通知中心發送給觀察者的消息,還有接收通知的名字,以 及指定的對象。通常來講不須要指定name和object,但若是僅僅指定了一個object,觀察者將收到該對象的全部通知。例如將上面的代碼中 name改成nil,那麼觀察者將接收到object對象的全部消息,可是肯定不了接收這些消息的順序。若是指指定一個通知名稱,觀察者將收到它每次發出 的通知。例如,上面的代碼中object爲nil,那麼客戶對象(self)將收到任何對象發出NSWindowDidBecomeMainNotification通知。若是既沒有指定指定object,也沒有指定name,那麼該觀察者將收到全部對象的全部消息。
分佈式
3.發送通知:postNotificationName:object:或者performSelectorOnMainThread:withObject:waitUntilDone:
例如程序能夠實現將一個文本能夠進行一系列的轉換,例如對於一個實例、RTF格式轉換成ASCII格式。而轉換在一個類(如Converter類)的對象中獲得處理,在誠尋執行過程當中能夠加入或者刪除這種轉換。並且當添加或者刪除Converter操做時,你的程序可能須要通知其餘的對象,可是這些Converter對象並不須要知道被通知對象是什麼,能幹什麼。你只須要聲明兩個通知,"ConverterAdded" 和 "ConverterRemoved",而且在某一事件發生時就發出這兩個通知。
當一個用戶安裝或者刪除一個Converter,它將發送下面的消息給通知中心:
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterAdded" object:self];
或者是
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterRemoved" object:self];
通知中心將會區分它們對象對這些通知感興趣而且通知他們。若是除了關心觀察者的通知名稱和觀察的對象,還關心其餘以外的對象,那麼就把以外的對象放在通知的可選字典中,或者用方法postNotificationName:object:userInfo:。
而採用performSelectorOnMainThread:withObject:waitUntilDone:則是直接調用NSNotification的方法postNotification,而postNotificationName和object參數能夠放到withObject的實參中。例如:
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];//注意這裏的notification爲自定義的一個通知對象,可定義爲NSNotification* notification = [NSNotification notificationWithName:@"ConverterAdded"object:self];//那麼它的做用與上面的一致
4.移除通知:removeObserver:和removeObserver:name:object:
其中,removeObserver:是刪除通知中心保存的調度表一個觀察者的全部入口,而removeObserver:name:object:是刪除匹配了通知中心保存的調度表中觀察者的一個入口。
這個比較簡單,直接調用該方法就行。例如:
[[NSNotificationCenter defaultCenter] removeObserver:observer name:nil object:self];
注意參數notificationObserver爲要刪除的觀察者,必定不能置爲nil。
PS:這裏簡單說一下通知中心保存的調度表。通知中心的調度表是給一些觀察者指定的一些通知集。一個通知集是通知中心發出的通知的子集。每一個表的入口包含:
通知觀察者(必需要的)、通知名稱、通知的發送者。
下圖表示通知集中指定的通知的調用表入口的四種類型:
下圖表示四種觀察者的調度表
最後,提醒一下觀察者收到通知的順序是沒有定義的。同時通知發出和觀察的對象有可能是同樣的。通知中心同步轉發通知給觀察者,就是說 postNotification: 方法直到接收並處理完通知才返回值。要想異步的發送通知,可使用NSNotificationQueue。在多線程編程中,通知通常是在一個發出通知的那個線程中轉發,但也多是不在同一個線程中轉發通知。
在對象間傳遞信息的標準方法是消息傳遞-即一個對象調用另外一個對象的方法。然而,消息傳遞要求發送消息的對象知道消息的接收者,以及它能夠響應什麼 消息。這個要求對於委託消息和其它類型的消息是能夠的。有些時候,咱們不但願兩個對象之間具備這種緊密的耦合-特別值得注意的緣由是它會把原本獨立的子系 統聯結在一塊兒。並且這種要求也是不切實際的,由於它須要把應用程序中不少全然不一樣的對象之間創建硬編碼的鏈接。
對於不能使用標準的消息傳遞的場合,Cocoa提供了通告廣播模型。經過通告機制,一個對象能夠通知其它對象本身正在幹什麼。在這個意義上,通告機 制相似於委託,可是它們之間的區別是很重要的。委託和通告的關鍵區別在於前者是一對一的通信路徑(在向外委託任務的對象和被委託的對象之間)。而通告是潛 在的一對多的通信方式-也就是一種廣播。一個對象只能有一個委託,但能夠有不少觀察者,由於通告的接收者是未知的。對象沒必要知道那些觀察者是什麼對象。任何對象均可以間接地經過通告來觀察一個事件,並經過調整本身的外觀、行爲、和狀態來響應事件。通告是一種在應用程序中進行協調和聚合的強大機制。
通告機制是如何工做的?這在概念上至關直接。在進程中有一個稱爲通告中心的對象,充當通告的信息交換和廣播中心。在應用程序的其它地方,須要知道某 個事件的對象在通告中心進行註冊,讓它知道當該事件發生時,本身但願獲得通知。這種場景的一個例子是當一個彈出式菜單被選擇時,控制器對象須要知道這個事 件,以便在用戶界面上對反映這個變化。當事件發生時,處理該事件的對象向通告中心發出一個通告,而後通告中心會將它派發給全部的相關的觀察者。圖5-8描述了這種機制。
請注意:通告中心同步地將通告派發給它的觀察者。發出通告的對象直到全部的通告被髮出後,才從新得到程序的控制權。若是須要以異步的方式發送通告,必須使用通告隊列(參見"通告隊列")。通告隊列在對特定的通告進行延遲處理,並根據某些具體的條件將相似的通告進行組合以後,纔將通告發給通告中心。
任何對象均可以發出通告,也能夠在通告中心註冊爲通告的觀察者。發出通告的對象、通告中包含的對象、還有通告的觀察者能夠是不一樣的對象,也能夠是同 一個對象(用同一個對象做爲通告的發送者和觀察者有它的用處,好比用於空閒處理方面)。發送通告的對象不須要知道通告觀察者的任何信息。另外一方面,觀察者 至少須要知道通告的名稱和通告對象中封裝的字典的鍵("通告對象" 部分描述了通告對象的是有什麼組成的)。
和委託同樣,通告機制是實現應用程序中的對象間通信的好工具。它使應用程序中的對象能夠了解其它地方發生的改變。通常地說,一個對象註冊爲通告的觀 察者,是由於它但願在相應的事件發生後或即將發生時進行調整。舉例來講,若是一個定製視圖但願在窗口調整尺寸的時候改變本身的外觀,則能夠觀察窗口對象發 出的NSWindowDidResizeNotification
通告。通告也容許在對象間傳遞信息,由於通告中能夠包含一個與事件相關的字典。
可是,通告和委託之間是不一樣的,這些差異也致使這兩種機制應該用於不一樣的地方。在早些時候提到,通告模型和委託模型的主要區別在於前者是廣播機制,而委託是一對一的關係。每種模型都有本身的優勢,通告機制的優勢以下:
發出通告的對象不須要知道觀察者的標識。
應用程序並不受限於Cocoa框架聲明的通告;任何類均可以聲明通告,其實例能夠發佈通告。
通告並不限於應用程序內部的通信;經過分佈式通告,一個進程能夠將發生的事件通知另外一個進程。
可是,一對一的委託模型也有本身的優勢。委託有機會經過將值返回給進行委託的對象來影響事件。另外一方面,通告的觀察者必須發揮更爲被動的做用,在響應事件時,它只能對自身及其環境產生影響。通告方法必須具備以下形式的簽名:
- (void)notificationHandlerName:(NSNotification *); |
這個要求使觀察對象沒法以任何直接的方式影響原來的事件。可是,委託一般能夠影響進行委託的對象對事件的處理方式。並且,Application Kit對象的委託自動註冊爲其通告的觀察者。您只須要實現框架類定義的通告方法,就能夠接收通告。
在Cocoa中,通告機制並非觀察對象狀態變化的惟一選擇,在不少狀況下甚至都不是最好的選擇。Cocoa綁定技術,特別是爲其提供支持的鍵-值 觀察(KVO)和鍵-值綁定(KVB)協議,也可使應用程序中的對象觀察其它對象性質的變化。綁定機制比通告機制更爲有效。在綁定機制中,被觀察對象和 觀察對象直接進行通信,不須要像通告中心這樣的中間對象。並且,綁定機制不會像常見的通告那樣,由於處理無觀察者的變化而對性能產生不利影響。
可是在某些場合中,選擇通告比選擇綁定更爲合理。您可能但願觀察事件,而不是對象性質的改變;或者,有些時候遵循KVO和KVB是不適合實際狀況的,特別是當須要發送和觀察的通告不多的時候。
即便在適合使用通告的場合下,您也應該知道它對性能的潛在影響。通告發出以後,最終會經過本地的通告中心同步地派發給觀察對象。無論通告的發送是同 步的仍是異步的,這個過程都是要發生的。若是有不少觀察者,或者每一個觀察者在處理通告時都作不少工做,您的程序就會有明顯的延遲。所以,您應該當心,不要 過分或低效地使用通告。最後,下面這些關於通告用法的原則應該有幫助:
對應用程序應該觀察的通告有所選擇。
註冊通告時,要具體到通告的名稱和發送的對象。
儘量高效地實現處理通告的方法。
避免添加或移除不少觀察者;經過一些「中間的」觀察者將通告的結果傳遞給它們能夠訪問的對象要好得多。
通告是一個對象,是NSNotification
的一個實例。該對象封裝了與事件有關的信息,好比某個窗口得到了焦點,或者某個網絡鏈接關閉了。當事件發生時,負責處理該事件的對象會向通告中心發出一個通告,通告中心則馬上將通告廣播給全部註冊的對象。
NSNotification
對象中包含一個名稱、一個對象、還有一個可選的字典。名稱是標識通告的標籤;對象是指通告的發送者但願發給通告觀察者的對象(它一般是通告發送者自己),相似於委託消息的發送者對象,通告的接收者能夠向該對象查詢更多的信息;字典則用於存儲與事件有關的信息。
通告中心負責發送和接受通告。它將通告通知給全部符合條件的觀察者。通告信息封裝在NSNotification
對象中。客戶對象在通告中心中將本身註冊爲特定通告的觀察者。當事件發生時,對象向通告中心發出相應的通告。而通告中心會向全部註冊過的觀察者派發消息,並將通告做爲惟一的參數進行傳遞。通告的發送對象和觀察對象有多是同一個對象。
Cocoa框架中包含兩種類型的通告中心:
請注意,NSNotificationCenter
和不少其它的Foundation類不一樣,不能和其在Core Foundation中對應的結構(CFNotificationCenterRef
)進行自由橋接。
每一個任務都有一個缺省的通告中心,您能夠經過NSNotificationCenter
的defaultCenter
類方法來進行訪問。通告中心在單任務中處理通告。若是須要在同一個機器的不一樣任務之間進行通信,可使用分佈式通告中心。
通告中心同步地將通告發送給觀察者。換句話說,在發出一個通告時,在全部的觀察者接收和處理完成通告以前,程序的控制權不會返回給發送者。若是須要異步發送通告,可使用通告隊列,這在"通告隊列"部分中進行描述。
在多線程的應用程序中,通告老是在發送的線程中傳送,這個線程可能不一樣於觀察者註冊所在的線程。
每一個任務都有一個缺省的分佈式通告中心,您能夠經過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:
方法。