一個NSNotificationCenter對象(通知中心)提供了在程序中廣播消息的機制,它實質上就是一個通知分發表。這個分發表負責維護爲各個通知註冊的觀察者,並在通知到達時,去查找相應的觀察者,將通知轉發給他們進行處理。html
本文主要了整理了一下NSNotificationCenter的使用及須要注意的一些問題,並提出了一些未解決的問題,但願能在此獲得解答。安全
每一個程序都會有一個默認的通知中心。爲此,NSNotificationCenter提供了一個類方法來獲取這個通知中心:多線程
+ (NSNotificationCenter *)defaultCenter
獲取了這個默認的通知中心對象後,咱們就可使用它來處理通知相關的操做了,包括註冊觀察者,移除觀察者、發送通知等。併發
一般若是不是出於必要,咱們通常都使用這個默認的通知中心,而不本身建立維護一個通知中心。app
若是想讓對象監聽某個通知,則須要在通知中心中將這個對象註冊爲通知的觀察者。早先,NSNotificationCenter提供瞭如下方法來添加觀察者:框架
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
這個方法帶有4個參數,分別指定了通知的觀察者、處理通知的回調、通知名及通知的發送對象。這裏須要注意幾個問題:async
對於以上幾點,咱們來重點關注一下第3條。如下代碼演示了當咱們的notificationName設置爲nil時,通知的監聽狀況。post
代碼清單1:添加一個Observer,其中notificationName爲nil性能
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)handleNotification:(NSNotification *)notification { NSLog(@"notification = %@", notification.name); } @end
運行後的輸出結果以下:測試
notification = TestNotification notification = UIWindowDidBecomeVisibleNotification notification = UIWindowDidBecomeKeyNotification notification = UIApplicationDidFinishLaunchingNotification notification = _UIWindowContentWillRotateNotification notification = _UIApplicationWillAddDeactivationReasonNotification notification = _UIApplicationDidRemoveDeactivationReasonNotification notification = UIDeviceOrientationDidChangeNotification notification = _UIApplicationDidRemoveDeactivationReasonNotification notification = UIApplicationDidBecomeActiveNotification
能夠看出,咱們的對象基本上監聽了測試程序啓動後的所示消息。固然,咱們不多會去這麼作。
而對於第4條,使用得比較多的場景是監聽UITextField的修改事件,一般咱們在一個ViewController中,只但願去監聽當前視圖中的UITextField修改事件,而不但願監聽全部UITextField的修改事件,這時咱們就能夠將當前頁面的UITextField對象指定爲notificationSender。
在iOS 4.0以後,NSNotificationCenter爲了跟上時代,又提供了一個以block方式實現的添加觀察者的方法,以下所示:
- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
你們第一次看到這個方法時是否會有這樣的疑問:觀察者呢?參數中並無指定具體的觀察者,那誰是觀察者呢?實際上,與前一個方法不一樣的是,前者使用一個現存的對象做爲觀察者,而這個方法會建立一個匿名的對象做爲觀察者(即方法返回的id<NSObject>對象),這個匿名對象會在指定的隊列(queue)上去執行咱們的block。
這個方法的優勢在於添加觀察者的操做與回調處理操做的代碼更加緊湊,不須要拼命滾動鼠標就能直接找處處理代碼,簡單直觀。這個方法也有幾個地方須要注意:
下面咱們重點說明一下第2點和第3點。
關於第2點,當咱們指定一個Operation Queue時,無論通知是在哪一個線程中post的,都會在Operation Queue所屬的線程中進行轉發,如代碼清單2所示:
代碼清單2:在指定隊列中接收通知
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSLog(@"receive thread = %@", [NSThread currentThread]); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"post thread = %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; }); } @end
在這裏,咱們在主線程裏添加了一個觀察者,並指定在主線程隊列中去接收處理這個通知。而後咱們在一個全局隊列中post了一個通知。咱們來看下輸出結果:
post thread = <NSThread: 0x7ffe0351f5f0>{number = 2, name = (null)} receive thread = <NSThread: 0x7ffe03508b30>{number = 1, name = main}
能夠看到,消息的post與接收處理並非在同一個線程中。如上面所提到的,若是queue爲nil,則消息是默認在post線程中同步處理,你們能夠試一下。
對於第3點,因爲使用的是block,因此須要注意的就是避免引發循環引用的問題,如代碼清單3所示:
代碼清單3:block引起的循環引用問題
@interface Observer : NSObject @property (nonatomic, assign) NSInteger i; @property (nonatomic, weak) id<NSObject> observer; @end @implementation Observer - (instancetype)init { self = [super init]; if (self) { NSLog(@"Init Observer"); // 添加觀察者 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSLog(@"handle notification"); // 使用self self.i = 10; }]; } return self; } @end #pragma mark - ViewController @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self createObserver]; // 發送消息 [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)createObserver { Observer *observer = [[Observer alloc] init]; } @end
運行後的輸出以下:
Init Observer handle notification
咱們能夠看到createObserver中建立的observer並無被釋放。因此,使用 – addObserverForName:object:queue:usingBlock:必定要注意這個問題。
與註冊觀察者相對應的,NSNotificationCenter爲咱們提供了兩個移除觀察者的方法。它們的定義以下:
- (void)removeObserver:(id)notificationObserver - (void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender
前一個方法會將notificationObserver從通知中心中移除,這樣notificationObserver就沒法再監放任何消息。然後一個會根據三個參數來移除相應的觀察者。
這兩個方法也有幾點須要注意:
關於註冊監聽者,還有一個須要注意的問題是,每次調用addObserver時,都會在通知中心從新註冊一次,即便是同一對象監聽同一個消息,而不是去覆蓋原來的監聽。這樣,當通知中心轉發某一消息時,若是同一對象屢次註冊了這個通知的觀察者,則會收到多個通知,如代碼清單4所示:
代碼清單4:同一對象屢次註冊同一消息
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)handleNotification:(NSNotification *)notification { NSLog(@"notification = %@", notification.name); } @end
其輸出結果以下所示:
notification = TestNotification notification = TestNotification
能夠看到對象處理了兩次通知。因此,若是咱們須要在viewWillAppear監聽一個通知時,必定要記得在對應的viewWillDisappear裏面將觀察者移除,不然就可能會出現上面的狀況。
最後,再特別重點強調的很是重要的一點是,在釋放對象前,必定要記住若是它監聽了通知,必定要將它從通知中心移除。若是是用 – addObserverForName:object:queue:usingBlock:,也記得必定得移除這個匿名觀察者。說白了就一句話,添加和移除要配對出現。
註冊了通知觀察者,咱們即可以隨時隨地的去post一個通知了(固然,若是閒着沒事,也能夠不註冊觀察者,post通知隨便玩,只是沒人理睬罷了)。NSNotificationCenter提供了三個方法來post一個通知,以下所示:
- postNotification: – postNotificationName:object: – postNotificationName:object:userInfo:
咱們能夠根據須要指定通知的發送者(object)並附帶一些與通知相關的信息(userInfo),固然這些發送者和userInfo能夠封裝在一個NSNotification對象中,由- postNotification:來發送。注意,- postNotification:的參數不能爲空,不然會引起一個異常,以下所示:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSNotificationCenter postNotification:]: notification is nil'
每次post一個通知時,通知中心都會去遍歷一下它的分發表,而後將通知轉發給相應的觀察者。
另外,通知的發送與處理是同步的,在某個地方post一個消息時,會等到全部觀察者對象執行完處理操做後,纔回到post的地方,繼續執行後面的代碼。如代碼清單5所示:
代碼清單5:通知的同步處理
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; NSLog(@"continue"); } - (void)handleNotification:(NSNotification *)notification { NSLog(@"handle notification"); } @end
運行後輸出結果是:
handle notification continue
翻了好些資料,還有兩個問題始終沒有明確的答案。
首先就是通知中心是如何維護觀察者對象的。能夠明確的是,添加觀察者時,通知中心沒有對觀察者作retain操做,即不會使觀察者的引用計數加1。那通知中心維護的是觀察者的weak引用呢仍是unsafe_unretained引用呢?
我的認爲多是unsafe_unretained的引用,由於咱們知道若是是weak引用,其所指的對象被釋放後,這個引用會被置成nil。而實際狀況是通知中心還會給這個對象發送消息,並引起一個異常。而若是向nil發送一個消息是不會致使異常的。
【很是感謝 @lv-pw,上面這個問題在《斯坦福大學公開課:iOS 7應用開發》的第5集的第57分50秒中獲得瞭解答:確實使用的是unsafe_unretained,老師的解釋是,之因此使用unsafe_unretained,而不使用weak,是爲了兼容老版本的系統。】
另外,咱們知道NSNotificationCenter實現的是觀察者模式,並且一般狀況下消息在哪一個線程被post,就在哪一個線程被轉發。而從上面的描述能夠發現, -addObserverForName:object:queue:usingBlock:添加的匿名觀察者能夠在指定的隊列中處理通知,那它的實現機制是什麼呢?
在咱們的應用程序中,一個大的話題就是兩個對象之間如何通訊。咱們須要根據對象之間的關係來肯定採用哪種通訊方式。對象之間的通訊方式主要有如下幾種:
通常狀況下,咱們能夠根據如下兩點來肯定使用哪一種方式:
Objective-C中的通知因爲其廣播性及鬆耦合性,很是適合於大的範圍內對象之間的通訊(模塊與模塊,或一些框架層級)。通知使用起來很是方便,也正由於如此,因此容易致使濫用。因此在使用前仍是須要多想一想,是否有更好的方法來實現咱們所須要的對象間通訊。畢竟,通知機制會在必定程度上會影響到程序的性能。
對於使用NSNotificationCenter,最後總結一些小建議: