iOS的通知(Notification)基本上都是以sync(同步)的方式發(fā)送的音诈,也就是說當(dāng)發(fā)送通知出去以后會先阻塞當(dāng)前線程,等到所有注冊該通知者接收到此通知并在當(dāng)前線程進行完相對應(yīng)的處理后花椭,程序才會繼續(xù)往下執(zhí)行。
為了測試這點忘朝,我們可以創(chuàng)建BYNotifyPoster和BYNotifyReceiver兩個類择同,分別負責(zé)發(fā)送和接收通知。在測試過程轨奄,我們分別在主線程和異步線程各發(fā)送一次通知孟害,測試接收者執(zhí)行的情況。
BYNotifyReceiver
//
// BYNotifyReceiver.h
// NotificationTest
//
// Created by Beryter on 2017/12/23.
// Copyright ? 2017年 Beryter. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface BYNotifyReceiver : NSObject
@end
#import "BYNotifyReceiver.h"
@implementation BYNotifyReceiver
- (instancetype)init
{
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"BYNotificationTest" object:nil];
}
return self;
}
/*!
* @brief 接收通知(BYNotificationTest)
* @param note NSNotification通知對象
*/
- (void)handleNotification:(NSNotification *)note
{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"接收到通知 - BYNotificationTest挪拟,主線程挨务,開始處理相關(guān)邏輯");
} else {
NSLog(@"接收到通知 - BYNotificationTest,異步線程玉组,開始處理相關(guān)邏輯");
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"BYNotificationTest" object:nil];
}
@end
BYNotifyPoster
//
// BYNotifyPoster.h
// NotificationTest
//
// Created by Beryter on 2017/12/23.
// Copyright ? 2017年 Beryter. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface BYNotifyPoster : NSObject
- (void)postNotification;
@end
#import "BYNotifyPoster.h"
@implementation BYNotifyPoster
/*!
* @brief 發(fā)送通知BYNotificationTest
*/
- (void)postNotification
{
NSLog(@"準備發(fā)送通知 - BYNotificationTest");
[[NSNotificationCenter defaultCenter] postNotificationName:@"BYNotificationTest" object:nil];
NSLog(@"BYNotificationTest - 通知發(fā)送完畢谎柄,繼續(xù)往下執(zhí)行");
// TODO:發(fā)送通知完畢后,繼續(xù)執(zhí)行
}
@end
我們在測試ViewController中加入以下代碼:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"------------主線程發(fā)送通知---------------");
[self.poster postNotification];
sleep(5);//延遲五秒
NSLog(@"------------異步線程發(fā)送通知--------------");
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = dispatch_queue_create("NOTIFICATION_TEST_QUEUE", NULL);
dispatch_async(queue, ^{
[weakSelf.poster postNotification];
});
}
運行后惯雳,測試結(jié)果如下:
2017-12-23 14:25:37.951231+0800 NotificationTest[4327:320244] ------------主線程發(fā)送通知---------------
2017-12-23 14:25:37.951388+0800 NotificationTest[4327:320244] 準備發(fā)送通知 - BYNotificationTest
2017-12-23 14:25:37.951496+0800 NotificationTest[4327:320244] 接收到通知 - BYNotificationTest朝巫,主線程,開始處理相關(guān)邏輯
2017-12-23 14:25:37.951604+0800 NotificationTest[4327:320244] BYNotificationTest - 通知發(fā)送完畢石景,繼續(xù)往下執(zhí)行
2017-12-23 14:25:42.952721+0800 NotificationTest[4327:320244] ------------異步線程發(fā)送通知--------------
2017-12-23 14:25:42.953099+0800 NotificationTest[4327:320290] 準備發(fā)送通知 - BYNotificationTest
2017-12-23 14:25:42.953337+0800 NotificationTest[4327:320290] 接收到通知 - BYNotificationTest劈猿,異步線程拙吉,開始處理相關(guān)邏輯
2017-12-23 14:25:42.953516+0800 NotificationTest[4327:320290] BYNotificationTest - 通知發(fā)送完畢,繼續(xù)往下執(zhí)行
根據(jù)運行后打印的log日志我們可以看到糙臼,BYNotifyPoster在發(fā)送完通知后庐镐,會等待所有被通知到的對象處理完相關(guān)事情后才會繼續(xù)向下執(zhí)行恩商,確實是以sync(同步)的方式進行的变逃。我們在看BYNotifyReceiver在接收到通知后打印的log日志,可以發(fā)現(xiàn)相關(guān)處理邏輯所在的線程和發(fā)送通知所在的線程是同一個線程怠堪。一句話就是揽乱,Notification是以sync(同步)的方式發(fā)送的,也就是說當(dāng)發(fā)送通知出去以后會先卡住當(dāng)前線程粟矿,等到所有注冊該通知者接收到此通知并在當(dāng)前線程(即發(fā)出通知的線程)進行完相對應(yīng)的處理后凰棉,程序才會繼續(xù)往下執(zhí)行。
注冊通知的另一個方法
我們繼續(xù)往下看陌粹,在注冊通知的API中或許我們還會發(fā)現(xiàn)一個比較有趣的API,如下:
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.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.
我們可以看到撒犀,此方法在給通知注冊觀察者的時候,加入了queue和block掏秩,簡化了代碼或舞,同樣也使代碼邏輯更加緊湊,代碼的可讀性也更高蒙幻。它并沒有指定observer,而是返回一個observer映凳。在這里,如果指定queue,block的執(zhí)行將在你指定的queue中執(zhí)行邮破,若沒有指定queue诈豌,block的執(zhí)行將在post消息線程中執(zhí)行。注意抒和,此處block會引起循環(huán)引用矫渔,使用過程中要使用weakSelf。關(guān)鍵代碼如下:
//將BYNotifyReceiver中代碼調(diào)整如下
- (instancetype)init
{
if (self = [super init]) {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"NOTIFICATION_HANDLE_QUEUE";
__weak typeof(self) weakSelf = self;
_notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"BYNotificationTest" object:nil queue:queue usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf testQueueNotification];
}];
}
return self;
}
- (void)testQueueNotification
{
[NSThread sleepForTimeInterval:5];//阻塞當(dāng)前線程5秒
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"接收到通知 - BYNotificationTest摧莽,主線程庙洼,開始處理相關(guān)邏輯,currentQueue = %@",[NSOperationQueue currentQueue].name);
} else {
NSLog(@"接收到通知 - BYNotificationTest,異步線程范嘱,開始處理相關(guān)邏輯,currentQueue = %@",[NSOperationQueue currentQueue].name);
}
// 注意移除通知的方式送膳,參數(shù)為notificationObserver,而不是self
[[NSNotificationCenter defaultCenter] removeObserver:self.notificationObserver];
}
//ViewController中代碼
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"------------主線程發(fā)送通知---------------");
[self.poster postNotification];
self.receiver = nil;
}
而log日志如下丑蛤,不用過多解釋請仔細看:
2017-12-23 16:37:21.559728+0800 NotificationTest[7607:678009] ------------主線程發(fā)送通知---------------
2017-12-23 16:37:21.559865+0800 NotificationTest[7607:678009] 準備發(fā)送通知 - BYNotificationTest
2017-12-23 16:37:26.565126+0800 NotificationTest[7607:678048] 接收到通知 - BYNotificationTest叠聋,異步線程,開始處理相關(guān)邏輯,currentQueue = NOTIFICATION_HANDLE_QUEUE
2017-12-23 16:37:26.565565+0800 NotificationTest[7607:678009] BYNotificationTest - 通知發(fā)送完畢受裹,繼續(xù)往下執(zhí)行
異步通知的實現(xiàn)
如何實現(xiàn)異步通知呢碌补?要想真正做到async(異步)發(fā)送通知虏束,則必須使用NSNotificationQueue。
一般來講厦章,這個是為了合并多個通知一起發(fā)送時使用的镇匀,當(dāng)然也能用作async發(fā)送通知。其中有幾個比較重要的點袜啃,要知道汗侵。
NSPostingStyle用來決定何時發(fā)送通知。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, //當(dāng)前runloop處于空閑或者waiting狀態(tài)時post
NSPostASAP = 2, //當(dāng)前runloop結(jié)束時post
NSPostNow = 3 //立即post群发,使用此模式相當(dāng)于sync!
};
NSNotificationCoalescing決定是否將多個通知進行合成晰韵,然后是以哪種方式合成。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, //不合并
NSNotificationCoalescingOnName = 1, //合并name(名稱)相同的通知
NSNotificationCoalescingOnSender = 2 //合并sender(發(fā)送者)相同的通知
};
還可以設(shè)定runloop mode熟妓。如果設(shè)定了runloop model則只會在特定的runloop mode下才會進行發(fā)送通知雪猪。
相關(guān)代碼如下:
BYNotifyReceiver.m
- (instancetype)init
{
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAsyncNotification:) name:@"BYAsyncNotificationTest" object:nil];
}
return self;
}
- (void)handleAsyncNotification:(NSNotification *)note
{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"接收到通知 - BYAsyncNotificationTest,主線程起愈,開始處理相關(guān)邏輯,currentQueue = %@",[NSOperationQueue currentQueue].name);
} else {
NSLog(@"接收到通知 - BYAsyncNotificationTest只恨,異步線程,開始處理相關(guān)邏輯,currentQueue = %@",[NSOperationQueue currentQueue].name);
}
}
BYNotifyPoster.m
/*!
* @brief 異步發(fā)送通知
*/
- (void)asyncPostNotification
{
NSLog(@"準備發(fā)送通知 - BYAsyncNotificationTest");
NSNotification *asyncNotification = [NSNotification notificationWithName:@"BYAsyncNotificationTest" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:asyncNotification postingStyle:NSPostASAP];
NSLog(@"BYAsyncNotificationTest - 通知發(fā)送完畢抬虽,繼續(xù)往下執(zhí)行");
// TODO:發(fā)送通知完畢后官觅,繼續(xù)執(zhí)行
[self testAsync];
}
- (void)testAsync
{
NSLog(@"%@", NSStringFromSelector(_cmd));
}
ViewController.m
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"------------主線程發(fā)送通知---------------");
[self.poster asyncPostNotification];
}
運行后,打印Log如下斥赋,答案一目了然:
2017-12-23 17:58:24.080311+0800 NotificationTest[9651:900465] ------------主線程發(fā)送通知---------------
2017-12-23 17:58:24.080736+0800 NotificationTest[9651:900465] 準備發(fā)送通知 - BYAsyncNotificationTest
2017-12-23 17:58:24.080926+0800 NotificationTest[9651:900465] BYAsyncNotificationTest - 通知發(fā)送完畢缰猴,繼續(xù)往下執(zhí)行
2017-12-23 17:58:24.081192+0800 NotificationTest[9651:900465] testAsync
2017-12-23 17:58:24.081644+0800 NotificationTest[9651:900465] 接收到通知 - BYAsyncNotificationTest,主線程疤剑,開始處理相關(guān)邏輯,currentQueue = NSOperationQueue Main Queue
至于如何合并通知滑绒,這里暫時不再講了,測試代碼就無需上傳了隘膘,留給想玩的同志們?nèi)ネ嬉晒省H绻ㄖ獛в衭serinfo參數(shù),此時通知被合并弯菊,userinfo又如何處理呢纵势,是否也會被合并呢,這個地方是比較有意思的管钳。如果被合并將會是什么樣子呢钦铁?如果不被合并,那通知接受者收到的userinfo中又是什么呢才漆?
本文為原創(chuàng)文章牛曹,轉(zhuǎn)載請注明出處。
可加群一起交流共同學(xué)習(xí):801216530醇滥。