iOS 通知原理解析

  • 通知的概念

一些基本的概念就不做介紹了,應(yīng)該都明白,好了,直接上代碼
為了方便查看,發(fā)送通知和接受通知就放在同一個(gè)文件里,一般項(xiàng)目不會(huì)這么用,但是操作都是一樣的
可以po一下 [[NSNotificationCenter defaultCenter],你會(huì)發(fā)現(xiàn)其實(shí)就是個(gè)列表,包含了系統(tǒng)通知,用戶通知等

- (void)viewDidLoad {
    [super viewDidLoad];
    //object:指定接受某個(gè)對(duì)象的通知,為nil表示可以接受任意對(duì)象的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotifi:) name:@"EdisonNotif" object:nil];
    
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [self sendNotification];
}


-(void)sendNotification{

    NSLog(@"發(fā)送通知before:%@",[NSThread currentThread]);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"EdisonNotif" object:nil];
    NSLog(@"發(fā)送通知after:%@",[NSThread currentThread]);
}


-(void)handleNotifi:(NSNotification*)notif{

    
    NSLog(@"接收到通知了:%@",[NSThread currentThread]);
}

打印結(jié)果:
/*
 2017-08-30 14:48:58.916 通知的底層解析[1422:94722] 發(fā)送通知before:<NSThread: 0x600000063500>{number = 1, name = main}
 2017-08-30 14:48:58.916 通知的底層解析[1422:94722] 接收到通知了:<NSThread: 0x600000063500>{number = 1, name = main}
 2017-08-30 14:48:58.917 通知的底層解析[1422:94722] 發(fā)送通知after:<NSThread: 0x600000063500>{number = 1, name = main}
 */

以上代碼就是我們平時(shí)所寫的發(fā)送注冊(cè)通知了一般寫法,點(diǎn)擊屏幕發(fā)送一次通知,看清楚打印順序,before->處理通知->after
這說明了是消息發(fā)完之后要等處理了消息才跑發(fā)送消息之后的代碼,這跟多線程中的同步概念相似,所以在同步的通知的方法里不要做耗時(shí)的操作,要不然就阻塞主線程

  • 使用(同步,異步)

上面介紹了同步,那么我們來說說異步的,就是我發(fā)送完之后就繼續(xù)跑下面的代碼,不需要去管接受通知的處理
比如異步,那么就要用到通知對(duì)列

//同步
-(void)sendNotification{

   //每個(gè)線程都默認(rèn)又一個(gè)通知隊(duì)列,可以直接獲取,也可以alloc
    NSNotificationQueue * notificationQueue = [NSNotificationQueue defaultQueue];
    
    NSNotification * notification = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
    //NSNotification * notificationTwo = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
 
/*
     (NSPostingStyle) 什么時(shí)候發(fā)送
     NSPostWhenIdle = 1,//空閑時(shí)發(fā)送
     NSPostASAP = 2,//盡快發(fā)送
     NSPostNow = 3,//現(xiàn)在發(fā)送
     
     
     <#(NSNotificationCoalescing)#>消息合并的方式
     NSNotificationNoCoalescing = 0, //不合并
     NSNotificationCoalescingOnName = 1,//按名稱合并
     NSNotificationCoalescingOnSender = 2,//按發(fā)送者合并
     
     */
    NSLog(@"異步發(fā)送通知before:%@",[NSThread currentThread]);
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    //[notificationQueue enqueueNotification:notificationTwo postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    //[notificationQueue enqueueNotification:notification postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    NSLog(@"異步發(fā)送通知after:%@",[NSThread currentThread]);
    
    /*
     2017-08-30 15:43:13.068 通知的底層解析[4206:132048] 異步發(fā)送通知before:<NSThread: 0x60000007e180>{number = 1, name = main}
     2017-08-30 15:43:13.068 通知的底層解析[4206:132048] 異步發(fā)送通知after:<NSThread: 0x60000007e180>{number = 1, name = main}
     2017-08-30 15:43:13.069 通知的底層解析[4206:132048] 接收到通知了:<NSThread: 0x60000007e180>{number = 1, name = main}
     */
}

如果把發(fā)送通知的方法sendNotification改成如上,打印的結(jié)果就是表明是異步發(fā)送了

再看下通知隊(duì)列跟線程的了關(guān)系,把點(diǎn)擊屏幕直接發(fā)送通知,改成開啟一個(gè)線程發(fā)送通知

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [NSThread detachNewThreadSelector:@selector(sendAsyncNotification) toTarget:self withObject:nil];
    /*
     2017-08-30 15:51:17.254 通知的底層解析[4244:137397] 異步發(fā)送通知before:<NSThread: 0x600000079480>{number = 3, name = (null)}
     2017-08-30 15:51:17.255 通知的底層解析[4244:137397] 異步發(fā)送通知after:<NSThread: 0x600000079480>{number = 3, name = (null)}
     */
}

看打印結(jié)果,發(fā)現(xiàn)根本沒有處理通知
再改下代碼,用線程發(fā)送同步的通知中心

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    //[self sendNotification];
    
    [NSThread detachNewThreadSelector:@selector(sendNotification) toTarget:self withObject:nil];
    //[self sendAsyncNotification];
    
}
/*
2017-08-30 15:54:49.932 通知的底層解析[4263:139861] 發(fā)送通知before:<NSThread: 0x60000026bc40>{number = 3, name = (null)}
2017-08-30 15:54:49.933 通知的底層解析[4263:139861] 接收到通知了:<NSThread: 0x60000026bc40>{number = 3, name = (null)}
2017-08-30 15:54:49.933 通知的底層解析[4263:139861] 發(fā)送通知after:<NSThread: 0x60000026bc40>{number = 3, name = (null)}
*/

而用線程發(fā)送同步的是可以接受到通知的,并且處理也是在線程里處理的
,這說通知隊(duì)列跟線程是有關(guān)系的,再繼續(xù)改代碼,回到線程發(fā)送異步通知,只是把發(fā)送時(shí)機(jī)改成馬上發(fā)送

[notificationQueue enqueueNotification:notification postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil];
/*
2017-08-30 15:59:19.426 通知的底層解析[4276:143034] 異步發(fā)送通知before:<NSThread: 0x60800006a180>{number = 3, name = (null)}
2017-08-30 15:59:19.426 通知的底層解析[4276:143034] 接收到通知了:<NSThread: 0x60800006a180>{number = 3, name = (null)}
2017-08-30 15:59:19.426 通知的底層解析[4276:143034] 異步發(fā)送通知after:<NSThread: 0x60800006a180>{number = 3, name = (null)}
*/

這又能處理通知,所以可以說NSPostNow就是同步,其實(shí)呢,[[NSNotificationCenter defaultCenter]通知中心這句代碼的意思就是:你在哪個(gè)線程里面就是獲取當(dāng)前線程的通知隊(duì)列并且默認(rèn)采用NSPostNow發(fā)送時(shí)機(jī)

 NSLog(@"異步發(fā)送通知before:%@",[NSThread currentThread]);
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
    NSLog(@"異步發(fā)送通知after:%@",[NSThread currentThread]);

那么通知隊(duì)列到底和線程有什么關(guān)系呢:每個(gè)線程都有一個(gè)通知隊(duì)列,當(dāng)線程結(jié)束了,通知隊(duì)列就被釋放了,所以當(dāng)前選擇發(fā)送時(shí)機(jī)為NSPostWhenIdle時(shí)也就是空閑的時(shí)候發(fā)送通知,通知隊(duì)列就已經(jīng)釋放了,所以通知發(fā)送不出去了
如果線程不結(jié)束,就可以發(fā)送通知了,用runloop讓線程不結(jié)束

//異步
-(void)sendAsyncNotification{

    //每個(gè)線程都默認(rèn)又一個(gè)通知隊(duì)列,可以直接獲取,也可以alloc
    NSNotificationQueue * notificationQueue = [NSNotificationQueue defaultQueue];
    
    NSNotification * notification = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
    
   
    NSLog(@"異步發(fā)送通知before:%@",[NSThread currentThread]);
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
    NSLog(@"異步發(fā)送通知after:%@",[NSThread currentThread]);
    
    NSPort * port = [NSPort new];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
    
    [[NSRunLoop currentRunLoop] run];
}
/*
2017-08-30 16:12:55.098 通知的底層解析[4394:153794] 異步發(fā)送通知before:<NSThread: 0x600000070200>{number = 3, name = (null)}
2017-08-30 16:12:55.098 通知的底層解析[4394:153794] 異步發(fā)送通知after:<NSThread: 0x600000070200>{number = 3, name = (null)}
2017-08-30 16:12:55.099 通知的底層解析[4394:153794] 接收到通知了:<NSThread: 0x600000070200>{number = 3, name = (null)}

*/

這樣通知就被發(fā)送出去了,而且發(fā)送和處理也在線程中,這還沒有達(dá)到真正的異步是吧,應(yīng)該發(fā)送在一個(gè)線程,處理在另一個(gè)線程

再來看下消息合并

//異步
-(void)sendAsyncNotification{

    //每個(gè)線程都默認(rèn)又一個(gè)通知隊(duì)列,可以直接獲取,也可以alloc
    NSNotificationQueue * notificationQueue = [NSNotificationQueue defaultQueue];
    
    NSNotification * notification = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
    NSNotification * notificationtwo = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
    
    /*
     <#(NSNotificationCoalescing)#>消息合并的方式
     NSNotificationNoCoalescing = 0, //不合并
     NSNotificationCoalescingOnName = 1,//按名稱合并
     NSNotificationCoalescingOnSender = 2,//按發(fā)送者合并
     
     */
    NSLog(@"異步發(fā)送通知before:%@",[NSThread currentThread]);
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [notificationQueue enqueueNotification:notificationtwo postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    NSLog(@"異步發(fā)送通知after:%@",[NSThread currentThread]);
    
    NSPort * port = [NSPort new];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
    
    [[NSRunLoop currentRunLoop] run];
}

/*
2017-08-30 16:22:16.149 通知的底層解析[4407:159094] 異步發(fā)送通知before:<NSThread: 0x608000265640>{number = 8, name = (null)}
2017-08-30 16:22:16.154 通知的底層解析[4407:159094] 異步發(fā)送通知after:<NSThread: 0x608000265640>{number = 8, name = (null)}
2017-08-30 16:22:16.154 通知的底層解析[4407:159094] 接收到通知了:<NSThread: 0x608000265640>{number = 8, name = (null)}

*/

設(shè)置成NSNotificationCoalescingOnName按名稱合并,此時(shí)我連續(xù)發(fā)送三條,但是只處理了一次,
再繼續(xù),上面代碼就只是把發(fā)送時(shí)機(jī)改成NSPostNow

    [notificationQueue enqueueNotification:notification postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [notificationQueue enqueueNotification:notificationTwo postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [notificationQueue enqueueNotification:notification postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
/*
2017-08-30 16:30:04.114 通知的底層解析[4442:164620] 異步發(fā)送通知before:<NSThread: 0x600000269a40>{number = 3, name = (null)}
2017-08-30 16:30:04.115 通知的底層解析[4442:164620] 接收到通知了:<NSThread: 0x600000269a40>{number = 3, name = (null)}
2017-08-30 16:30:04.115 通知的底層解析[4442:164620] 接收到通知了:<NSThread: 0x600000269a40>{number = 3, name = (null)}
2017-08-30 16:30:04.115 通知的底層解析[4442:164620] 接收到通知了:<NSThread: 0x600000269a40>{number = 3, name = (null)}
2017-08-30 16:30:04.116 通知的底層解析[4442:164620] 異步發(fā)送通知after:<NSThread: 0x600000269a40>{number = 3, name = (null)}
*/

結(jié)果就打印了處理了三次通知,這個(gè)應(yīng)該好理解吧,就跟dispatch_sync原理一樣,就是得發(fā)送因?yàn)镹SPostNow是同步的,所以發(fā)送第一條通知,得等處理完第一條通知,才跑發(fā)送第二條通知,這樣肯定就沒有合并消息一說了,因?yàn)檫@有點(diǎn)類似線程阻塞的意思,只有異步,就是三個(gè)發(fā)送通知全部跑完,在處理通知的時(shí)候看是否需要合并和怎么合并,再去處理

那么系統(tǒng)哪些消息是合并的

drawRect

  • 通知隊(duì)列(線程關(guān)系,消息合并等,系統(tǒng)哪些消息做了合并)

  • 底層通信port(同線程處理,不同線程處理)

通知隊(duì)列也可以實(shí)現(xiàn)異步,但是真正的異步還是得通過port

NSPort,底層所有的消息觸發(fā)都是通過端口來進(jìn)行操作的

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotifi:) name:@"EdisonNotif" object:nil];
    
    _port  =[[NSPort alloc] init];
    //消息處理通過代理來處理的
    _port.delegate=self;
    //把端口加在哪個(gè)線程里,就在哪個(gè)線程進(jìn)行處理,下面:加在當(dāng)前線程的runloop里
    [[NSRunLoop currentRunLoop] addPort:_port forMode:NSRunLoopCommonModes];
    
}

//發(fā)送消息
-(void)sendPort{

    NSLog(@"port發(fā)送通知before:%@",[NSThread currentThread]);

    [_port sendBeforeDate:[NSDate date] msgid:1212 components:nil from:nil reserved:0];
 
    NSLog(@"port發(fā)送通知after:%@",[NSThread currentThread]);
}

//處理消息
- (void)handlePortMessage:(NSPortMessage *)message{

    
    NSLog(@"port處理任務(wù):%@",[NSThread currentThread]);
    NSObject * messageObj = (NSObject*)message;
    NSLog(@"=%@",[messageObj valueForKey:@"msgid"]);
}


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    
    [self sendPort];
    
}
/*
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] port發(fā)送通知before:<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] port發(fā)送通知after:<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] port處理任務(wù):<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] =1212
*/

這樣顯然全部都在一個(gè)線程里,修改下代碼

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [NSThread detachNewThreadSelector:@selector(sendPort) toTarget:self withObject:nil];
  /*

*/
}
  • 分析層次結(jié)構(gòu)(通知中心)

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

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,789評(píng)論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 一、多線程 說明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)。 NEW:這種情況指的是,通過 New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,849評(píng)論 0 44
  • 冉姐寫了思想碎片,我覺得這個(gè)方法好,其實(shí)日子就是這樣,有時(shí)候是被打碎了,有時(shí)候是碎片居然組成了一副亮閃閃的畫。我也...
    冠世墨玉yanzi閱讀 321評(píng)論 3 4
  • 關(guān)于軍訓(xùn)的那些事 提起參加軍訓(xùn)這事,我想,大家心中應(yīng)該都是一萬個(gè)不愿意吧。其實(shí),在參加我的第一次軍訓(xùn)前,我也是這么...
    樂子閱讀 454評(píng)論 0 2

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