NSNotification學(xué)習(xí)

主要分為NSNotification、NSNotificationCenter和底層隊列NSNotificationQueue。

優(yōu)點:跨層通信、解耦。

使用注意點:盡量將所有的通知名和觀察對象的關(guān)聯(lián)關(guān)系放置在獨立文件中,方便維護與查找。

NSNotification

包含了通知名name、發(fā)送通知的對象object以及保存信息userInfo
其中,NSNotificationName作為區(qū)分通知的存在。

一般通過NSNotificationCenter的相關(guān)API來自動創(chuàng)建通知對象,無需手動創(chuàng)建(NSNotification是類簇,調(diào)用init初始化會拋異常)。

NSNotificationCenter

系統(tǒng)默認(rèn)為APP通過實現(xiàn)了名為defaultCenter的單例通知中心對象。所有的通知(跨層,跨線程)默認(rèn)都通過此對象進行通知的分發(fā)。

系統(tǒng)收到post通知請求時,會掃描注冊到NSNotificationCenter對象中的所有觀察者分發(fā)表。因此在大量頻繁使用通知進行通信時,可以考慮使用自定義的NSNotificationCenter對象來提高性能。

系統(tǒng)提供了target+selectorblock+queue兩種方式來向NSNotificationCenter注冊觀察者。

默認(rèn)在哪個線程發(fā)送通知(post),就在哪個線程執(zhí)行回調(diào)。除非通過block的方式發(fā)送通知,指定執(zhí)行的隊列。

注意

在block+queue的方式中,由于執(zhí)行時機的不同(runloop的不同時機)NSNotificationCenter會將block拷貝到堆中進行保留。因此需要注意對象的捕獲即內(nèi)存引用循環(huán)問題。

NSNotificationCenter的同步執(zhí)行

不管接收通知的觀察者回調(diào)在什么線程上執(zhí)行,通過NSNotificationCenter調(diào)用postNotification時都是同步執(zhí)行的

- (void)viewDidLoad {
    [super viewDidLoad];

    // 主線程監(jiān)聽
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testSelector1) name:@"jiji.notification.test" object:nil];
   
   // 后臺線程監(jiān)聽
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testSelector1) name:@"jiji.notification.test" object:nil];
    });
    
    // 主線程監(jiān)聽,block方式,強制在主隊列執(zhí)行(主線程)
    [[NSNotificationCenter defaultCenter] addObserverForName:@"jiji.notification.test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"%s", __func__);
        NSLog(@"thread - %@", [NSThread currentThread]);
    }];
    
   
    // 在后臺線程發(fā)送通知
    dispatch_queue_t queue = dispatch_queue_create("com.jiji.queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"jiji.notification.test" object:nil];
        NSNotificationQueue
        
        NSLog(@"post finish----");
    });
}

- (void)testSelector1 {
    NSLog(@"%s", __func__);
    NSLog(@"thread - %@", [NSThread currentThread]);
}

執(zhí)行結(jié)果:

2019-09-05 13:39:36.594370+0800 TestAPP[52913:2455622] -[ViewController testSelector1]
2019-09-05 13:39:36.594797+0800 TestAPP[52913:2455622] thread - <NSThread: 0x6000001bd640>{number = 4, name = (null)}
2019-09-05 13:39:36.595147+0800 TestAPP[52913:2455622] -[ViewController testSelector1]
2019-09-05 13:39:36.595537+0800 TestAPP[52913:2455622] thread - <NSThread: 0x6000001bd640>{number = 4, name = (null)}
2019-09-05 13:39:36.596740+0800 TestAPP[52913:2455552] -[ViewController viewDidLoad]_block_invoke
2019-09-05 13:39:36.597015+0800 TestAPP[52913:2455552] thread - <NSThread: 0x6000001d1f00>{number = 1, name = main}
2019-09-05 13:39:36.597354+0800 TestAPP[52913:2455622] post finish----

可以看到:

  1. 除block版本監(jiān)聽外,其他的通知回調(diào)執(zhí)行線程都與發(fā)送通知的線程相同(保證線程安全)。
  2. NSNotificationCenter主動發(fā)送通知為同步操作:在所有監(jiān)聽者的回調(diào)執(zhí)行結(jié)束之后,被阻塞的發(fā)送操作才向下繼續(xù)執(zhí)行。因此,對于消耗性能或長時間的操作需要盡量避免。

發(fā)送同步通知的過程,就是直接的消息發(fā)送

NSNotificationQueue的異步調(diào)用

那如果監(jiān)聽方需要執(zhí)行的任務(wù)確實需要長時間怎么辦?除了可以在回調(diào)中將任務(wù)放在子線程中運行(GCD或NSOperationQueue),F(xiàn)oundation框架本身也提供了一個異步發(fā)送通知的方式,也就是NSNotificationQueue。

NSNotificationQueue,顧名思義,是一個保存著NSNotification的FIFO隊列。

每個線程都有自己默認(rèn)的NSNotificationQueue對象,可以與NSNotificationCenter進行關(guān)聯(lián),通過center異步發(fā)送通知。

  • NSNotificationQueue可以根據(jù)線程所屬的runloop確定通知發(fā)送的時機:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // runloop空閑時發(fā)送
    NSPostASAP = 2, // runloop中盡快發(fā)送(as soon as possible)
    NSPostNow = 3 // 實時發(fā)送(直接通過NSNotificationCenter發(fā)送)
};
  • NSNotificationQueue可以根據(jù)類型將相同的通知進行合并發(fā)送。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0, // 不合并
    NSNotificationCoalescingOnName = 1, // 同名通知合并
    NSNotificationCoalescingOnSender = 2 // 相同object合并
};

完整類定義如下:

@interface NSNotificationQueue : NSObject {
@private
    // 關(guān)聯(lián)的NSNotificationCenter對象
    id  _notificationCenter; 
    // runloop盡快發(fā)送隊列
    id  _asapQueue;
    // 盡快發(fā)送隊列對應(yīng)的首個觀察者
    id  _asapObs;
    // runloop空閑時刻隊列
    id  _idleQueue;
    // 空閑時刻隊列對應(yīng)的首個觀察者
    id  _idleObs;
}

/** 默認(rèn)隊列 */
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;

/** 綁定NSNotificationCenter對象并初始化自身 */
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;

/** NSNotification入隊,并指定發(fā)送時機(根據(jù)時機進入不同的私有隊列) */
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
/** 針對指定的runloop的mode,NSNotification入隊,并指定發(fā)送時機及合并方式 */
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

/** NSNotification出隊,并指定合并方式 */
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

@end

根據(jù)實現(xiàn),根據(jù)私有成員變量,可以猜想一下:

  1. NSNotificationQueue可能只是一個對象封裝,內(nèi)部的_asapQueue和_idleQueue才是真正的隊列容器。根據(jù)入隊NSNotification對象時設(shè)置的配置信息,插入到相應(yīng)的隊列中。
  2. 在NSNotification對象出隊準(zhǔn)備執(zhí)行時,需要在綁定的NSNotificationCenter的分發(fā)表中查詢出通知名對應(yīng)的觀察者信息(應(yīng)該包含觀察者對象及回調(diào)SEL),賦值給如_asapObs的觀察者信息指針,runloop則通過此觀察者進行通知回調(diào)。

作為對比,看個發(fā)送異步通知的例子:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testSelector1) name:@"jiji.notification.test" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testSelector2) name:@"jiji.notification.test" object:nil];

    // 強制主線程執(zhí)行
    [[NSNotificationCenter defaultCenter] addObserverForName:@"jiji.notification.test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"%s", __func__);
        NSLog(@"thread - %@", [NSThread currentThread]);
    }];
    
    
    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
        ^{
            // 在runloop的mcurrentMode中插入item(source1),防止線程退出
            [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];

            // 在子線程發(fā)送異步通知(異步通知依賴runloop)
            NSNotification *noti = [NSNotification notificationWithName:@"jiji.notification.test" object:nil];
            [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];

            NSLog(@"post finish---");

            [[NSRunLoop currentRunLoop] run];
        }
    );
}

- (void)testSelector1 {
    NSLog(@"%s", __func__);
    NSLog(@"thread - %@", [NSThread currentThread]);
}

- (void)testSelector2 {
    NSLog(@"%s", __func__);
    NSLog(@"thread - %@", [NSThread currentThread]);
}

執(zhí)行結(jié)果:

2019-09-05 16:48:29.744888+0800 TestAPP[56766:2652270] post finish---
2019-09-05 16:48:29.745274+0800 TestAPP[56766:2652270] -[ViewController testSelector1]
2019-09-05 16:48:29.745815+0800 TestAPP[56766:2652270] thread - <NSThread: 0x6000015193c0>{number = 5, name = (null)}
2019-09-05 16:48:29.746069+0800 TestAPP[56766:2652270] -[ViewController testSelector2]
2019-09-05 16:48:29.746229+0800 TestAPP[56766:2652270] thread - <NSThread: 0x6000015193c0>{number = 5, name = (null)}
2019-09-05 16:48:29.825634+0800 TestAPP[56766:2652197] -[ViewController viewDidLoad]_block_invoke
2019-09-05 16:48:29.841702+0800 TestAPP[56766:2652197] thread - <NSThread: 0x600001562c00>{number = 1, name = main}

可以看到,在將NSNotification對象入隊后,方法直接返回,繼續(xù)向下執(zhí)行。而通知則通過runloop在適當(dāng)?shù)臅r刻進行發(fā)出。

其中,若修改入隊參數(shù)coalesceMask為NSNotificationCoalescingOnName,即可實現(xiàn)對于回調(diào)頻率的控制(高頻多次調(diào)用只發(fā)送一個通知)。對于參數(shù)postingStyle,若設(shè)置為NSPostNow,則為同步消息發(fā)送,與postNotification相同。

而且,通過調(diào)用棧信息可以看出,發(fā)送異步通知的過程,實際上是在線程runloop的指定mode中插入了一個Observer的item,待需要執(zhí)行時進行調(diào)用。

NSNotification與KVO的區(qū)別

KVO是在對象的keyPath上添加觀察者的,而NSNotification是在通知名上添加觀察者。

NSNotification使用范圍廣,更加靈活,只不過需要主動發(fā)送通知才能觸發(fā)給觀察者。

參考資料:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容