介紹
Notification在Cocoa中是一個解藕的機制。假如你有一個對象,是一個網絡監視器,此時你想告訴其他的對象,網絡數據已經下載完畢。這個過程你可以使用很多方法來實現。
你可以下一個網絡監聽器的子類,並重載handleDisConnect方法。你也可以用target/action的方式去建立一個網絡監聽器,並關聯自己的實現方法。你也可以用責任鏈的方式,或者用delegate的方式。還可以用這篇所介紹的方式:Notification。
許多實現方式都是1對1的方式,一種更好的方法是通過notification的方式在對象之間建立間接的關係,他們之間通過廣播的方式進行通知。Notification center就像是在對象之間建立一個轉發機構,當一個通知被髮送到Notification center中,所有想要接受消息的對象就能接收到該消息。
右側的三個對象(App Delegate,data viewer和logging system)都告訴Notification center他們對網絡下載通知感興趣。當網絡下載完畢後,網絡監聽器告訴Notification center把這個消息通知給感興趣的對象。通知方和接收方之間沒有直接的關聯,他們是通過廣播的形式進行的。這種方式也被稱作發現者模式
notification center像是一箇中間人,對象可以通過它自由的像外界發送通知。這些發送通知的類不需要去繼承特殊的類,也不需要實現特殊的接口。得益於OC的runtime機制,接收通知的類可以自定義任意selector去處理這些通知,而不需要實現任何接口和callback函數。
註冊Notification
接收通知的類需要告訴notification center他們對某些感興趣。notification center就會講這些類和他們的處理函數紀錄在一個內部的列表中,就像是一個電話本。當你開始編程前,這個列表基本上就是一個空的。
然後在一個對象中寫上註冊notification的表達式,告訴notification center它對某一個通知感興趣。
[code]
// AppDelegate
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self
selector: @selector(networkByeBye:)
name: kNetworkWentByeByeNotification
object: self.networkMonitor];
上面代碼中,AppDelegate告訴notification center,當網絡中斷的時候,它想要接受到通知,也許它可能嘗試重新連接。注意,代碼中,AppDelegate對一個名叫kNetworkWentByeByeNotification的通知感興趣,這是一個NSString類型的對象。如果networkMonitor發送一個名叫kNetworkWentByeByeNotification的通知,則AppDelegate就會用networkByeBye:去處理這個通知。Notification center會記錄下AppDelegate,networkMonitor和selector的地址,並用kNetworkWentByeByeNotification來進行標註。
此時,對象DataViewer對象想要接收網絡下載完畢的通知,然後可以改變用戶界面,它也用相似的方式註冊通知。
[code]
// DataViewer
[center addObserver: self
selector: @selector(handleNetworkChange:)
name: kNetworkWentByeByeNotification
object: nil];
上面代碼中Object傳的參數是nil。這說明只要有任何對象發出kNetworkWentByeByeNotification的通知,它都能接收到,這點與AppDelegate不同,他只能接收來自networkMonitor的通知。
Notification center這個時候會在kNetworkWentByeByeNotification這個標籤下增加一條記錄,改記錄中存儲了DataViewer和selector的地址。
最後,logging system也想接受通知,這次使用使用一個較現代的API來註冊通知,其中使用到了block和operation queue的技術。
[code]
// LogOTron
// _token is an instance variable of type 'id'
_token = [center addObserverForName: kNetworkWentByeByeNotification
object: nil
queue: [NSOperationQueue mainQueue]
usingBlock: ^(NSNotification *notification) {
NSLog (@"Network went down: %@", notification);
}];
上述代碼是要告訴notification center,“當任何對象發送kNetworkWentByeByeNotification通知,就將block放到這個queue中去執行”,notification center會將這和對象的註冊信息像前兩次一樣進行記錄。
這個現代API與前兩種最大的不同就是,沒有對應的類和對象來處理這個notitification,前兩種註冊方式必須有一個對應的類或者對象來處理這個notification,而最後一種只需要提供一個block就就可以了,通過notification center中的記錄信息也能看到最後一種方式的特殊之處。
發送Nofication
現在已經寫好了註冊通知的信息,但是什麼效果都沒有看到。notification center只是記錄了有哪些對象對哪些通知感興趣的信息,但是還沒有對象來發送通知。
netword下載完成後,向notification center發送通知。
[code]
// NetworkMonitor
- (void) networkWentDown {
// Collect the error
// Pack the error and other information into a dictionary
NSDictionary *userInfo = ...;
// Post notification.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
} // networkWentDown
最後一行代碼,告訴notification center發送一個名爲kNetworkWentByeByeNotification的通知給感興趣的對象。notification center接收到消息之後,通知kNetworkWentByeByeNotification標籤下面的對象來接受通知,這個對象接收到通知之後,用之前註冊好的selector或者queue來處理通知。
AppDelegate接收到消息之後,會用networkByeBye來處理通知。Logging system接受到通知後,將block放進main queue中執行。
註銷通知
當一個對象對某一個通知不感興趣之後,它應該從notification center中將註冊信息註銷掉。註銷後,notification center就會刪除關於改對象的記錄信息,並斷開和改selector的弱引用,但是如果使用的是block PAI的話,它還會保留對block的引用。所以這個時候要注意是否存在循環引用。當不再接收某個通知的時候,應該儘快的註銷掉,或者在對象的dealloc時註銷通知。
註銷通知非常容易:
[code]
[center removeObserver: self];
上面的代碼是將self中的所有註冊的通知都註銷掉,爲了能比較安全的進行註銷,因爲指定註銷特定的通知,因爲有時候有些通知是深藏於Coca中的,此時如果註銷掉可能會造成系統異常。
註銷制定的通知
[code]
[center removeObserver: self
name: kNetworkWentByeByeNotification
object: nil];
上面的代碼只是將kNetworkWentByeByeNotification通知註銷掉的,但是其他的仍然保留。
Logging system中的通知註銷要稍微麻煩一些,因爲這個通知和改對象沒有任何關聯,用上面兩種方式是無法註銷的。所以需要在註冊block型的通知時,要將註冊信息保留下來,然後在你不需要的時候,在通過這個保留的信息將通知註銷掉,上面代碼中,是用了一個token的變量保留block API的,這個token是一個notificationcenter類型的變量。
這種block類型的API應該用下面這種方式進行註銷:
[code]
[center removeObserver: _token];