1、Notification
-
通知中心實際上是在程序內部提供了消息廣播的一種機制,它允許我們在低程度耦合的情況下,滿足控制器與一個任意的對象進行通信的目的。每一個 iOS 程序(即每一個進程)都有一個自己的通知中心,即 NSNotificationCenter 對象,該對象采用單例設計模式,可以通過類方法 defaultCenter 獲得當前進程唯一的通知中心對象。一個 NSNotificationCenter 可以有許多的通知消息 NSNotification,對於每一個 NSNotification 可以有很多的觀察者 Observer 來接收通知。NSNotificationCenter 是 iOS 中通知中心的靈魂,由該類實現了觀察者模式,並給開發者提供了諸如注冊、刪除觀察者的接口。
-
通知中心以同步的方法將消息轉發到所有的觀察者中,換言之 NSNotificationCenter 在發送消息后,會一直等待被調用的方法執行完畢,然后返回控制權到主函數中,再接着執行后面的功能,即這是一個同步阻塞的操作。如果我們需要異步的處理消息,直接返回控制權,則應該使用通知隊列 NSNotificationQueue,在子線程中將通知加入到通知隊列中,在多線程程序中,通知會被分發到每一個發送消息的線程中,這可能與觀察者注冊時所在的線程已經不是同一個線程。
-
任何一個對象都可以向通知中心發布通知(NSNotification),描述自己在做什么。其他感興趣的對象(Observer)可以申請在某個特定通知發布時(或在某個特定的對象發布通知時)收到這個通知。

-
使用 [NSNotificationCenter defaultCenter] 發送的通知無論是在主線程還是子線程中被注冊,觀察者注冊的選擇器方法都會在主線程中被執行。執行順序為:main runloop -> 發送通知 -> 觀察者選 擇器方法(按照觀察者注冊的順序執行)-> 通知發送者方法中其它的操作 -> main runloop。
-
在子線程中使用 [NSNotificationQueue defaultQueue] 將通知加入到通知隊列中,觀察者選擇器方法就會在子線程中被執行。子線程執行順序為:發送通知 -> 觀察者選擇器方法(按照觀察者注冊的順序同 步執行)-> 通知發送者方法中其它的操作。
-
如果要在同一台機器上進行進程間的通信,需要使用 NSDistributedNOtificationCenter。
-
優勢:
- 1、不需要編寫多少代碼,實現比較簡單;
- 2、對於一個發出的通知,多個對象能夠做出反應,即 1 對多的方式實現簡單;
- 3、controller 能夠傳遞 context 對象(dictionary),context 對象攜帶了關於發送通知的自定義的信息。
-
缺點:
- 1、在編譯期不會檢查通知是否能夠被觀察者正確的處理;
- 2、在釋放注冊的對象時,需要在通知中心取消注冊;
- 3、在調試的時候應用的工作以及控制過程難跟蹤;
- 4、需要第三方來管理 controller 與觀察者對象之間的聯系;
- 5、controller 和觀察者需要提前知道通知名稱、UserInfo dictionary keys。如果這些沒有在工作區間定義,那么會出現不同步的情況;
- 6、通知發出后,controller 不能從觀察者獲得任何的反饋信息。
-
目的:
- 降低兩個子系統之間的偶合度。
-
方式:
- 一個對象發送通知給通知中心,通知中心以廣播的形式通知所有的監聽者。
-
通知和代理的區別:
-
共同點:
- 利用通知和代理都能完成對象之間的通信(比如 A 對象告訴 D 對象發生了什么事情, A 對象傳遞數據給 D 對象)
-
不同點:
- 代理 : 1 個對象只能告訴另 1 個對象發生了什么事情。
- 通知 :
- 1 個對象能告訴 N 個對象發生了什么事情, 1 個對象能得知 N 個對象發生了什么事情。
- 任何數量的對象都可以接收同一個消息,而不僅限定於委托對象。
- 通知系統不接受返回值。
- 對象不需要預先定義協議方法,就可以接收來自通知中心的消息。
- 發布通知的對象只負責發布通知,不需要關心觀察者是否存在。
-
-
通知中心是同步的,還是異步的 ?
- 同步的。當發生通知時,通知中心廣播,有可能有多個監聽者,設計上使用同步的方式,能夠保證所有的監聽者都對通知作出響應,不會產生遺漏。
2、系統發送 Notification 的使用
-
系統發送 Notification,用戶不需要手動發送通知,設置的事件觸發時,系統自動發送通知。
-
通知中心不會保留(retain)監聽器對象,在通知中心注冊過的對象,必須在該對象釋放前取消注冊。否則,當相應的通知再次出現時,通知中心仍然會向該監聽器發送消息。因為相應的監聽器對象已經被釋放了,所以可能會導致應用崩潰。一般在監聽器銷毀之前取消注冊(如在監聽器中加入下列代碼):
- (void)dealloc { // [super dealloc]; // 非 ARC 中需要調用此句 [[NSNotificationCenter defaultCenter] removeObserver:self]; } -
在注冊、移除通知時,通知名稱標示(aName)使用系統定義的標示。
-
注冊通知(觀察者)
-
Objective-C
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; -
Swift
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.playFinished), name:AVPlayerItemDidPlayToEndTimeNotification, object: nil)
-
-
移除通知(觀察者)
-
Objective-C
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; -
Swift
NSNotificationCenter.defaultCenter().removeObserver(self, name:AVPlayerItemDidPlayToEndTimeNotification, object:nil)
-
3、自定義發送 Notification 的使用
-
使用 [NSNotificationCenter defaultCenter] 發送的通知無論是在主線程還是子線程中被注冊,觀察者注冊的選擇器方法都會在主線程中被執行。
-
執行順序:main runloop -> 發送通知 -> 觀察者選擇器方法(按照觀察者注冊的順序執行)-> 通知發送者方法中其它的操作 -> main runloop
-
通知(消息)的創建
+ (instancetype)notificationWithName:(NSString *)aName object:(nullable id)anObject; + (instancetype)notificationWithName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo; public convenience init(name aName: String, object anObject: AnyObject?) public init(name: String, object: AnyObject?, userInfo: [NSObject : AnyObject]?) 參數說明: aName :通知名稱 anObject :傳遞給觀察者的任意對象,通知發布者(是誰要發布通知) aUserInfo:傳遞的消息內容,自定義字典,可以傳遞更多附加信息,一些額外的信息(通知發布者傳遞給通知接收者的信息內容)-
Objective-C
// 不帶消息內容 NSNotification *notification1 = [NSNotification notificationWithName:@"notification1" object:self]; // 帶消息內容 NSNotification *notification2 = [NSNotification notificationWithName:@"notification2" object:self userInfo:@{@"name":_name, @"age":_age}]; -
Swift
// 不帶消息內容 let notification1 = NSNotification(name: "notification1", object: self) // 帶消息內容 let notification2 = NSNotification(name: "notification2", object: self, userInfo: ["name":name, "age":age])
-
-
發送通知
- (void)postNotification:(NSNotification *)notification; - (void)postNotificationName:(NSString *)aName object:(nullable id)anObject; - (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo; public func postNotification(notification: NSNotification) public func postNotificationName(aName: String, object anObject: AnyObject?) public func postNotificationName(aName: String, object anObject: AnyObject?, userInfo aUserInfo: [NSObject : AnyObject]?) 參數說明: notification:發送的通知(消息) aName :通知名稱 anObject :傳遞給觀察者的任意對象,通知發布者 aUserInfo :傳遞的消息內容,自定義字典,可以傳遞更多附加信息-
Objective-C
// 發送創建好的消息 [[NSNotificationCenter defaultCenter] postNotification:notification1]; // 直接發送消息,不帶消息內容 [[NSNotificationCenter defaultCenter] postNotificationName:@"notification3" object:self]; // 直接發送消息,帶消息內容 [[NSNotificationCenter defaultCenter] postNotificationName:@"notification4" object:self userInfo:@{@"name":_name, @"age":_age}]; -
Swift
// 發送創建好的消息 NSNotificationCenter.defaultCenter().postNotification(notification1) // 直接發送消息,不帶消息內容 NSNotificationCenter.defaultCenter().postNotificationName("notification3", object: self) // 直接發送消息,帶消息內容 NSNotificationCenter.defaultCenter().postNotificationName("notification4", object: self, userInfo: ["name":name, "age":age])
-
-
注冊通知(觀察者)
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject; public func addObserver(observer: AnyObject, selector aSelector: Selector, name aName: String?, object anObject: AnyObject?) 參數說明: observer :觀察者,即誰要接收這個通知; aSelector:收到通知后調用何種方法,即回調函數,並且把通知對象當做參數傳入; aName :通知的名字,也是通知的唯一標示,編譯器就通過這個找到通知的。 為 nil 時,表示注冊所有通知,那么無論通知的名稱是什么,監聽器都能收到這個通知; anObject :通知發送者,為 nil 時,表示監聽所有發送者的通知。如果 anObject 和 aName 都為 nil,監聽器都收到所有的通知。 - (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block; 參數說明: name :通知的名稱 obj :通知發布者 queue:決定了 block 在哪個操作隊列中執行,如果傳 nil,默認在當前操作隊列中同步執行 block:收到對應的通知時,會回調這個 block-
Objective-C
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notification1Sel) name:@"notification1" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notification2Sel:) name:@"notification2" object:nil]; // 通知觸發方法,通知無內容 - (void)notification1Sel { } // 通知觸發方法,通知有內容 - (void)notification2Sel:(NSNotification *)notification { // 接收用戶消息內容 NSDictionary *userInfo = notification.userInfo; } -
Swift
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.notification1Sel), name: "notification1", object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.notification2Sel(_:)), name: "notification2", object: nil) // 通知觸發方法,通知無內容 func notification1Sel() { } // 通知觸發方法,通知有內容 func notification2Sel(notification:NSNotification) { // 接收用戶消息內容 let userInfo = notification.userInfo }
-
-
移除通知(觀察者)
- (void)removeObserver:(id)observer; - (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject; public func removeObserver(observer: AnyObject) public func removeObserver(observer: AnyObject, name aName: String?, object anObject: AnyObject?) 參數說明: observer:觀察者,即在什么地方接收通知; aName :通知的名字,也是通知的唯一標示,編譯器就通過這個找到通知的。 anObject:通知發送者,為 nil 時,表示移除滿足條件的所有發送者的通知。-
Objective-C
// 移除此觀察者的所有通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除指定名字的通知 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"notification1" object:nil]; -
Swift
// 移除此觀察者的所有通知 NSNotificationCenter.defaultCenter().removeObserver(self) // 移除指定名字的通知 NSNotificationCenter.defaultCenter().removeObserver(self, name:"notification1", object:nil)
-
4、異步發送 Notification 的使用
-
在子線程中使用 [NSNotificationQueue defaultQueue] 將通知加入到通知隊列中,觀察者選擇器方法就會在子線程中被執行。
| -> -> -> -> -> -> -> -> main runloop -> -> -> -> -> -> -> -> -> -> | 執行順序:main runloop -> | | -> main runloop | -> 發送通知 -> 觀察者選擇器方法(按照觀察者注冊的順序同步執行)-> 通知發送者方法中其它的操作 | -
發送異步通知
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle; - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSString *> *)modes; 參數說明: notification:通知 postingStyle:發布方式 coalesceMask:合並方式 modes :運行循環模式,nil 表示 NSDefaultRunLoopMode NSPostingStyle :發布方式 NSPostWhenIdle = 1, :空閑時發布 NSPostASAP = 2, :盡快發布 NSPostNow = 3 :立即發布 NSNotificationCoalescing :合並方式 NSNotificationNoCoalescing = 0, :不合並 NSNotificationCoalescingOnName = 1, :按名稱合並 NSNotificationCoalescingOnSender = 2 :按發布者合並-
Objective-C
// 創建通知 NSNotification *asyncNotification = [NSNotification notificationWithName:@"asyncNotification" object:self]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 將通知添加到發送隊列中,發送通知 [[NSNotificationQueue defaultQueue] enqueueNotification:asyncNotification postingStyle:NSPostWhenIdle]; });
-
-
移除異步通知
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask; 參數說明: notification:通知 coalesceMask:合並方式-
Objective-C
// 移除通知,不是立即發布的通知可以被移除 [[NSNotificationQueue defaultQueue] dequeueNotificationsMatching:asyncNotification coalesceMask:0];
-
5、系統通知的使用
5.1 UIDevice 通知
-
UIDevice 類提供了一個單例對象,它代表着設備,通過它可以獲得一些設備相關的信息,比如電池電量值(batteryLevel)、電池狀態(batteryState)、設備的類型(model,比如 iPod、iPhone 等)、設備的系統(systemVersion)。通過 [UIDevice currentDevice] 可以獲取這個單例對象。
-
UIDevice 對象會不間斷地發布一些通知,下列是 UIDevice 對象所發布通知的名稱常量:
UIDeviceOrientationDidChangeNotification // 設備旋轉 UIDeviceBatteryStateDidChangeNotification // 電池狀態改變 UIDeviceBatteryLevelDidChangeNotification // 電池電量改變 UIDeviceProximityStateDidChangeNotification // 近距離傳感器(比如設備貼近了使用者的臉部)
5.2 鍵盤通知
-
我們經常需要在鍵盤彈出或者隱藏的時候做一些特定的操作,因此需要監聽鍵盤的狀態。
-
鍵盤狀態改變的時候,系統會發出一些特定的通知:
UIKeyboardWillShowNotification // 鍵盤即將顯示 UIKeyboardDidShowNotification // 鍵盤顯示完畢 UIKeyboardWillHideNotification // 鍵盤即將隱藏 UIKeyboardDidHideNotification // 鍵盤隱藏完畢 UIKeyboardWillChangeFrameNotification // 鍵盤的位置尺寸即將發生改變 UIKeyboardDidChangeFrameNotification // 鍵盤的位置尺寸改變完畢 -
系統發出鍵盤通知時,會附帶一下跟鍵盤有關的額外信息(字典),字典常見的 key 如下:
UIKeyboardFrameBeginUserInfoKey // 鍵盤剛開始的 frame UIKeyboardFrameEndUserInfoKey // 鍵盤最終的 frame(動畫執行完畢后) UIKeyboardAnimationDurationUserInfoKey // 鍵盤動畫的時間 UIKeyboardAnimationCurveUserInfoKey // 鍵盤動畫的執行節奏(快慢)
