# 前言
Cocoa中使用NSNotification、NSNotificationCenter和KVO來實現觀察者模式,實現對象間一對多的依賴關系。
本篇文章主要來討論NSNotification和NSNotificationCenter
# NSNotification
NSNotification是方便NSNotificationCenter廣播到其他對象時的封裝對象,簡單講即通知中心對通知調度表中的對象廣播時發送NSNotification對象。
@interface NSNotification : NSObject
@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
NSNotification對象包含名稱、object、字典三個屬性,名稱是用來標識通知的標記,object是要通知的對象可以為nil,字典用來存儲發送通知時附帶的信息,也可以為nil。
# NSNotificationCenter
NSNotificationCenter是類似一個廣播中心站,使用defaultCenter來獲取應用中的通知中心,它可以向應用任何地方發送和接收通知。在通知中心注冊觀察者,發送者使用通知中心廣播時,以NSNotification的name和object來確定需要發送給哪個觀察者。為保證觀察者能接收到通知,所以應先向通知中心注冊觀察者,接着再發送通知這樣才能在通知中心調度表中查找到相應觀察者進行通知。
發送者
發送通知可使用以下方法發送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
-
三種方式實際上都是發送NSNotification對象給通知中心注冊的觀察者。
-
發送通知通過name和object來確定來標識觀察者,name和object兩個參數的規則相同即當通知設置name為kChangeNotifition時,那么只會發送給符合name為kChangeNotifition的觀察者,如果name為nil那么就會對所有的觀察者發送通知,同理object指發送給某個特定對象通知,當設置為nil時表示所有對象都會通知。那么如果同時設置name和object參數時就必須同時符合這兩個條件的觀察者才能接收到通知。相反的如果兩個參數都為nil那么就是所有觀察者都會收到通知。
觀察者
你可以使用以下兩種方式注冊觀察者
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id )addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
// The return value is retained by the system, and should be held onto by the caller in
// order to remove the observer with removeObserver: later, to stop observation.
-
第一種方式是比較常用的添加Oberver的方式,接到通知時執行aSelector。
-
第二種方式是基於Block來添加觀察者,往通知中心的調度表中添加觀察者,這個觀察者包括一個queue和一個block,並且會返回這個觀察者對象。當接到通知時執行block所在的線程為添加觀察者時傳入的queue參數,queue也可以為nil,那么block就在通知所在的線程同步執行。
這里需要注意的是如果使用第二種的方式創建觀察者需要弱引用可能引起循環引用的對象,避免內存泄漏。
移除觀察者
在對象被釋放前需要移除掉觀察者,避免已經被釋放的對象還接收到通知導致崩潰。
移除觀察者有兩種方式:
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
-
傳入相應的需要移除的observer 或者使用第二種方式三個參數來移除指定某個觀察者。
-
如果使用基於-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]方法在獲取方法返回的觀察者進行釋放。基於這個方法我們還可以讓觀察者接到通知后只執行一次:
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kChangeNotifition object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]");
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
-
在iOS9中調整了NSNotificatinonCenter
NSNotificationCenter.png
iOS9開始不需要在觀察者對象釋放之前從通知中心移除觀察者了。但是如果使用-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]方法還是需要手動釋放。因為NSNotificationCenter依舊對它們強引用。
# NSNotificationQueue
NSNotificationQueue通知隊列,用來管理多個通知的調用。通知隊列通常以先進先出(FIFO)順序維護通。NSNotificationQueue就像一個緩沖池把一個個通知放進池子中,使用特定方式通過NSNotificationCenter發送到相應的觀察者。下面我們會提到特定的方式即合並通知和異步通知。
-
創建通知隊列方法:
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;
-
往隊列加入通知方法:
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray *)modes;
-
移除隊列中的通知方法:
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
發送方式
-
NSPostingStyle包括三種類型:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1,
NSPostASAP = 2,
NSPostNow = 3
};
NSPostWhenIdle:空閑發送通知 當運行循環處於等待或空閑狀態時,發送通知,對於不重要的通知可以使用。
NSPostASAP:盡快發送通知 當前運行循環迭代完成時,通知將會被發送,有點類似沒有延遲的定時器。
NSPostNow :同步發送通知 如果不使用合並通知 和postNotification:一樣是同步通知。
合並通知
-
NSNotificationCoalescing也包括三種類型:
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0,
NSNotificationCoalescingOnName = 1,
NSNotificationCoalescingOnSender = 2
};
NSNotificationNoCoalescing:不合並通知。
NSNotificationCoalescingOnName:合並相同名稱的通知。
NSNotificationCoalescingOnSender:合並相同通知和同一對象的通知。
-
通過合並我們可以用來保證相同的通知只被發送一次。
-
forModes:(nullable NSArray *)modes可以使用不同的NSRunLoopMode配合來發送通知,可以看出實際上NSNotificationQueue與RunLoop的機制以及運行循環有關系,通過NSNotificationQueue隊列來發送的通知和關聯的RunLoop運行機制來進行的。
# NSNotificatinonCenter實現原理
-
NSNotificatinonCenter是使用觀察者模式來實現的用於跨層傳遞消息,用來降低耦合度。
-
NSNotificatinonCenter用來管理通知,將觀察者注冊到NSNotificatinonCenter的通知調度表中,然后發送通知時利用標識符name和object識別出調度表中的觀察者,然后調用相應的觀察者的方法,即傳遞消息(在Objective-C中對象調用方法,就是傳遞消息,消息有name或者selector,可以接受參數,而且可能有返回值),如果是基於block創建的通知就調用NSNotification的block。
# 參考資料
Notification Programming Topics
NSNotificationCenter part 4: Asynchronous notifications with NSNotificationQueue
NSNotification & NSNotification Center