Notification鸳玩,項(xiàng)目中使用還是蠻多的,post發(fā)送通知演闭,addObserver監(jiān)聽接收通知不跟,聽起來很簡單,對吧米碰。但是還是會有些大家可能會忽視的地方窝革。
同步or異步
[NotificationCenter defaultCenter] postNotification]购城,這種方式是同步
的,并且在哪個線程發(fā)虐译,就在哪個線程收瘪板。
同步的意思,就是消息接收者全部處理完消息之后漆诽,post這方才會繼續(xù)往下執(zhí)行侮攀,因此,盡量不要做太耗時的操作拴泌。
由于消息收和發(fā)都在同一個線程中
魏身。所以,盡量在主線程中
post蚪腐,不然會引起不必要的麻煩箭昵,ui刷新問題,崩潰問題等等回季。
addObserver調(diào)用多次
addObserver如果添加多次家制,當(dāng)post的時候,也會收到多次泡一。類似這種:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
observer的移除
這個是老生常談了颤殴,一定記得要移除,否則崩潰很容易發(fā)生鼻忠。不過在iOS 9以后涵但,不需要手動移除。
If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method帖蔓。
異步通知
異步的好處矮瘟,不必等待所有的消息處理者處理完成,可以立馬返回塑娇。
如果要發(fā)送異步通知澈侠,可以使用NSNotificationQueue
,將通知enqueueNotification
之后埋酬,會在合適的時候?qū)⑼ㄖl(fā)送給NotificationCenter哨啃,NotificationCenter會真正的將消息post出去。
[[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"task" object:self] postingStyle:NSPostWhenIdle];
可以指定runloop mode写妥,當(dāng)runloop處理該種mode的時候拳球,才會發(fā)送通知。
也可以指定發(fā)送通知的時機(jī)珍特,有如下3種醇坝。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1,
NSPostASAP = 2,
NSPostNow = 3
};
NSPostWhenIdle,在runloop空閑時發(fā)送次坡,當(dāng)runloop要退出時呼猪,不會發(fā)送。
NSPostASAP:Posting As Soon As Possible砸琅,在runloop的當(dāng)前迭代完成時發(fā)送給通知中心宋距,但是當(dāng)前mode和設(shè)定的mode要一致衰腌。
NSPostNow:就是同步調(diào)用莉掂。
聚合發(fā)送
當(dāng)在一段時間內(nèi),enqueue了多個通知岛杀,系統(tǒng)不會每個都發(fā)送诱篷,如果在隊(duì)列中已有該種通知壶唤,則不會進(jìn)入隊(duì)列,只保留第一個通知棕所。有如下3中可選方式闸盔。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不聚合
NSNotificationCoalescingOnName = 1, // 根據(jù)通知名聚合
NSNotificationCoalescingOnSender = 2 // 根據(jù)發(fā)送者聚合
};
NSNotificationCoalescingOnName:根據(jù)通知名稱來聚合,如果在一段時間內(nèi)琳省,
NotificationName="UpdateMyProfileNotification"有多個迎吵,則將他們聚合起來,只發(fā)送一個针贬。
NSNotificationCoalescingOnSender:根據(jù)發(fā)送方來聚合击费。
我做了下測試,的確只收到一次通知桦他。并且是第一個通知蔫巩。
for (int i = 0; i < 2; i++) {
NSNotification *notification = [NSNotification notificationWithName:@"TestNotification" object:@(i)];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
}
在指定線程接收通知
上面說到,收發(fā)都在一個線程中快压,如果想要做到圆仔,在某個指定的線程接收通知,該如何做呢嗓节?蘋果文檔上提及了實(shí)現(xiàn)思路荧缘。
先簡單介紹下mach port,它主要用來線程間通信拦宣。簡單來說截粗,就是接收線程中注冊NSMachPort,在另外的線程中使用此port發(fā)送消息鸵隧,則注冊線程會收到相應(yīng)消息绸罗,調(diào)用handleMachMessage來處理。
主要思路:
定義一個中間對象NotificationHandler豆瘫,用來專門接收通知珊蟀,包括一個隊(duì)列,接收通知的線程,mach port育灸,lock腻窒。
首先NotificationHandler會注冊一個通知,對應(yīng)的處理函數(shù)為processNotification磅崭,當(dāng)在其他線程中post時儿子,processNotification會被調(diào)用砸喻,進(jìn)行如下處理柔逼。如果收到的通知跟指定的線程一樣,則處理消息割岛,反之愉适,則添加到隊(duì)列,同時通過port發(fā)送消息給指定線程癣漆。注意
多線程中维咸,對隊(duì)列的處理,要加鎖
扑媚。指定線程收到回調(diào)handleMachMessage腰湾,首先會將通知刪除,然后調(diào)用processNotification進(jìn)行處理疆股,繼續(xù)以上過程费坊。
- (instancetype)init {
if (self = [super init]) {
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"notification" object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setUpThreadingSupport
{
if (self.notifications) {
return;
}
self.notifications = [NSMutableArray new];
self.lock = [NSLock new];
self.thread = [NSThread currentThread];
self.port = [NSMachPort new];
[self.port setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:self.port forMode:(__bridge NSString *) kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg
{
[self.lock lock];
// 由于大量port message在同時被發(fā)送時,可能會被丟棄旬痹,為了防止沒有處理到附井,這里遍歷數(shù)組來進(jìn)行處理。
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex:0];
[self.notifications removeObjectAtIndex:0];
[self.lock unlock];
[self processNotification:notification];
[self.lock lock];
}
[self.lock unlock];
}
- (void)processNotification:(NSNotification *)notification
{
NSThread *ct = [NSThread currentThread];
// 不是指定線程
if (ct != _thread) {
[self.lock lock];
// 添加到隊(duì)列
[self.notifications addObject:notification];
[self.lock unlock];
// 通過mach port發(fā)送消息
[self.port sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
}else{
NSLog(@"process notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
}
}
如果两残,我們想要在主線程中接收通知永毅,在viewDidLoad中。
- (void)viewDidLoad {
self.notificationHandler = [NotificationHandler new];
// 在子線程中post人弓,會在主線程中收到
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
});
}
或者沼死,需要在某個子線程中接收,可以這樣崔赌。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
[self.thread start];
}
- (void)startThread {
NSLog(@"startThread %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
self.notificationHandler = [NotificationHandler new];
// 在另外的子線程中發(fā)送通知dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
});
// 線程中需要自己啟動runloop
[[NSRunLoop currentRunLoop] run];
}
參考:
Notifications