Notification

Notification的命名方式及定義方法

[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

Apple范例:

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

在發(fā)送通知的實現(xiàn)文件中,按如下方式定義:

NSNotificationName const 通知名 = @"text notification";

在需要接收改通知的類文件的頂部按如下方式聲明該通知變量:

UIKIT_EXTERN NSNotificationName const 通知名;

NSNotificationName==NSString *
UIKIT_EXTERN==extern

具體詳情可以參考下面大神的鏈接。

參考來源:http://www.itdecent.cn/p/761f302c0bd5

NSNotification用法

總結自南峰子的技術博客
總結自天口三水羊的簡書

添加觀察者的兩種方式:

方式一:

- (void)addObserver:(id)notificationObserver
           selector:(SEL)notificationSelector
               name:(NSString *)notificationName
             object:(id)notificationSender
  1. notificationObserver不能為nil。
  2. notificationSelector回調方法有且只有一個參數(shù)(NSNotification對象)。
  3. 如果notificationName為nil,則會接收所有的通知(如果notificationSender不為空,則接收所有來自于notificationSender的所有通知)。
  4. 如果notificationSender為nil,則會接收所有notificationName定義的通知;否則,接收由notificationSender發(fā)送的通知。
  5. 監(jiān)聽同一條通知的多個觀察者,在通知到達時,它們執(zhí)行回調的順序是不確定的,所以我們不能去假設操作的執(zhí)行會按照添加觀察者的順序來執(zhí)行。

方式二:

- (id<NSObject>)addObserverForName:(NSString *)name
                            object:(id)obj
                             queue:(NSOperationQueue *)queue
                        usingBlock:(void (^)(NSNotification *note))block
  1. name和obj為nil時的情形與前面一個方法是相同的。

  2. 如果queue為nil,則消息是默認在post線程中同步處理,即通知的post與轉發(fā)是在同一線程中;但如果我們指定了操作隊列,不管通知是在哪個線程中post的,都會在Operation Queue所屬的線程中進行轉發(fā)。

  3. block塊會被通知中心拷貝一份(執(zhí)行copy操作),以在堆中維護一個block對象,直到觀察者被從通知中心中移除。所以,應該特別注意在block中使用外部對象,避免出現(xiàn)對象的循環(huán)引用。

  4. 如果一個給定的通知觸發(fā)了多個觀察者的block操作,則這些操作會在各自的Operation Queue中被并發(fā)執(zhí)行。所以我們不能去假設操作的執(zhí)行會按照添加觀察者的順序來執(zhí)行。
    該方法會返回一個表示觀察者的對象,記得在不用時釋放這個對象。

  5. 關于注冊監(jiān)聽者,還有一個需要注意的問題是,每次調用addObserver時,都會在通知中心重新注冊一次,即使是同一對象監(jiān)聽同一個消息,而不是去覆蓋原來的監(jiān)聽。這樣,當通知中心轉發(fā)某一消息時,如果同一對象多次注冊了這個通知的觀察者,則會收到多個通知。

移除觀察者的方式

- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver
                                    name:(NSString *)notificationName
                                   object:(id)notificationSender
  1. 由于注冊觀察者時(不管是哪個方法),通知中心會維護一個觀察者的弱引用,所以在釋放對象時,要確保移除對象所有監(jiān)聽的通知。否則,可能會導致程序崩潰或一些莫名其妙的問題。

  2. 對于第二個方法,如果notificationName為nil,則會移除所有匹配notificationObserver和notificationSender的通知,同理notificationSender也是一樣的。而如果notificationName和notificationSender都為nil,則其效果就與第一個方法是一樣的了。

  3. 最有趣的應該是這兩個方法的使用時機。–removeObserver:適合于在類的dealloc方法中調用,這樣可以確保將對象從通知中心中清除;而在viewWillDisappear:這樣的方法中,則適合于使用-removeObserver:name:object:方法,以避免不知情的情況下移除了不應該移除的通知觀察者。例如,假設我們的ViewController繼承自一個類庫的某個ViewController類(假設為SKViewController吧),可能SKViewController自身也監(jiān)聽了某些通知以執(zhí)行特定的操作,但我們使用時并不知道。如果直接在viewWillDisappear:中調用–removeObserver:,則也會把父類監(jiān)聽的通知也給移除。

post消息

- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:

  1. 每次post一個通知時,通知中心都會去遍歷一下它的分發(fā)表,然后將通知轉發(fā)給相應的觀察者。
  2. 通知的發(fā)送與處理是同步的,在某個地方post一個消息時,會等到所有觀察者對象執(zhí)行完處理操作后,才回到post的地方,繼續(xù)執(zhí)行后面的代碼。

通知中心是如何維護觀察者對象的

上面這個問題在《斯坦福大學公開課:iOS 7應用開發(fā)》的第5集的第57分50秒中得到了解答:確實使用的是unsafe_unretained,老師的解釋是,之所以使用unsafe_unretained,而不使用weak,是為了兼容老版本的系統(tǒng)。

iOS8及以前,NSNotificationCenter持有的是觀察者的unsafe_unretained指針(可能是為了兼容老版本),這樣,在觀察者回收的時候未removeOberser,而后再進行post操作,則會向一段被回收的區(qū)域發(fā)送消息,所以出現(xiàn)野指針crash。而iOS9以后,unsafe_unretained改成了weak指針,即使dealloc的時候未removeOberser,再進行post操作,則會向nil發(fā)送消息,所以沒有任何問題。

Notification Queues和異步通知

異步通知原理
創(chuàng)建一個NSNotificationQueue隊列(first in-first out),將定義的NSNotification放入其中,并為其指定三種狀態(tài)之一:

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,      // 當runloop處于空閑狀態(tài)時post
    NSPostASAP = 2,    // 當當前runloop完成之后立即post
    NSPostNow = 3    // 立即post,同步(為什么需要這種type,且看三.3)
};

異步通知的使用

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
    //[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle];
    //[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow];
    NSLog(@"測試同步還是異步");
    return YES;
}

// 輸出
2017-02-26 19:56:32.805 notification[19406:12719309] 測試同步還是異步
2017-02-26 19:56:32.816 notification[19406:12719309] selector 1
2017-02-26 19:56:32.816 notification[19406:12719309] block 2

Notification Queues的合成作用

NSNotificationQueue除了有異步通知的能力之外,也能對當前隊列的通知根據(jù)NSNotificationCoalescing類型進行合成(即將幾個合成一個)。

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,  // 不合成
    NSNotificationCoalescingOnName = 1,  // 根據(jù)NSNotification的name字段進行合成
    NSNotificationCoalescingOnSender = 2  // 根據(jù)NSNotification的object字段進行合成
};

指定Thread處理通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_async(queue, ^{
        NSLog(@"current thread %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil];
    });
    return YES;
}

可知,a中的observer的selector將會在DISPATCH_QUEUE_PRIORITY_BACKGROUND中執(zhí)行,若該selector執(zhí)行的是刷新UI的操作,那么這種方式顯然是錯誤的。這里,我們需要保證selector永遠在mainThread執(zhí)行。所以,有以下方式,指定observer的回調方法的執(zhí)行線程:

// 代碼
@interface A : NSObject 
- (void)test;
@end
@implementation A
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
    [[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
        // 刷新UI ...
    }];
}
@end
 
// 輸出
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017-02-27 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} 刷新UI

對象之間的通信方式主要有以下幾種:

  1. 直接方法調用
  2. Target-Action
  3. Delegate
  4. 回調(block)
  5. KVO
  6. 通知
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容