接收與發送 Notification
接收 Notification
一個通知分成幾個部分
- object: 發送者,是誰送出了這個通知
- name: 這個通知叫做什麼名字
- user info: 這個通知還帶了哪些額外資訊
所以,當我們想要監聽某個通知的時候,便是指定要收聽由誰所發出、哪個名字的通知,並且指定負責處理通知的 selector,以前面處理 locale 改變的例子來看,我們就會寫出這樣的 code:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(localeDidChange:)
name:NSCurrentLocaleDidChangeNotification
object:nil];
}
- (void)localeDidChange:(NSNotification *)notification
{
// 處理 locale 改變的狀況
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
意思就是,我們要指定 name 為 NSCurrentLocaleDidChangeNotification 的通知,交由 localeDidChange:
處理。在這邊 object 設定為 nil,代表不管是哪個物件送出的,只要符合 NSCurrentLocaleDidChangeNotification 的通知,我們統統都處理。
每個通知當中,還可能會有額外的資訊,會夾帶在 NSNotification 物件的 userInfo 屬性中,userInfo 是一個 NSDictionary。
像是我們如果讓某個 text field 變成了 first responder,那麼,在 iOS 上,就會出現螢幕鍵盤,而當螢幕鍵盤出現的時候,我們往往要調整畫面的 layout,而螢幕鍵盤在不同輸入法下的大小不一樣,像是跟英文鍵盤比較起來,中文輸入鍵盤上面往往會有一塊組字區,而造成中文鍵盤比英文鍵盤大。在螢幕鍵盤升起來的時候,我們會收到 UIKeyboardWillShowNotification 這項通知,而這項通知就會用 userInfo 告訴我們鍵盤尺寸與位置。
如果我們在 -addObserver:selector:name:object:
裡頭,把 name 指定成
nil,就代表我們想要訂閱所有的通知,通常不太會有這種情境,不過有時候你想要知道系統內部發生了什麼事情,可以用這種方式試試看。
當我們不需要繼續訂閱某項通知的時候,記得對 Notification Center 呼叫
-removeObserver:
,以上面的程式為例,我們在 add observer 的時候傳入了
self,在 remove observer 的時候,就要傳入 self。我們通常在 dealloc 的時候停止訂閱。
在 iOS 4 與 Mac OS X 10.6 之後,我們可以使用
-addObserverForName:object:queue:usingBlock:
這組使用 block 語法的
API 訂閱通知,由於是傳入 block,所以我們就不必另外準備一個 selector,可以將處理 notification 的程式與 add observer 的這段呼叫寫在一起。而
remove observer 的寫法也會不太一樣:
-addObserverForName:object:queue:usingBlock:
會回傳一個 observer 物件,我們想要停止訂閱通知的時候,是對 -removeObserver:
傳入之前拿到的
observer 物件。範例如下。
Add observer 的時候:
self.observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSCurrentLocaleDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
// 處理 locale 改變的狀況
}];
Remove observer 的時候:
[[NSNotificationCenter defaultCenter] removeObserver:self.observer];
發送 Notification
至於要發送 notification,則是在建立了 notification 物件之後,對
NSNotificationCenter 呼叫 -postNotification:
即可。
這三組 method 都可以用來發送 notification。
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName
object:(id)anObject;
- (void)postNotificationName:(NSString *)aName
object:(id)anObject
userInfo:(NSDictionary *)aUserInfo;
Notification 與 Threading
當我們訂閱某個 notification 之後,我們並不能夠保證負責處理 notification 的 selector 或 block 會在哪個 thread 執行:這個 notification 是在哪條 thread 送出的,負責接受的 selector 或是 block,就會在哪條 thread 執行。
在慣例上,絕大多數的 notification 都會在 main thread 送出,之所以說「絕大多數」,就是因為有例外:像是在 iOS 上,如果我們接上耳機、拔除耳機,或是將音樂透過 AirPlay 送到 Apple TV 的時候,系統會透過 AVAudioSessionRouteChangeNotification 告訴我們音訊輸出設備改變了1,這個通知就會發生在背景,而不是 main thread。
不過,當我們在撰寫自己的程式,要發送 notification 的時候,為了考慮其他開發者會預期在 main thread 收到 notification,所以我們也就在 main
thread 發送 notification,像是透過 GCD,把 postNotification 的呼叫送到
dispatch_get_main_queue()
上。
1. 參見蘋果官方文件 Responding to Route Changes - https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/HandlingAudioHardwareRouteChanges/HandlingAudioHardwareRouteChanges.html ↩