iOS 通知原理解析

  • 通知的概念

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

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

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

    [self sendNotification];
}


-(void)sendNotification{

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


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

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

打印結果:
/*
 2017-08-30 14:48:58.916 通知的底層解析[1422:94722] 發送通知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] 發送通知after:<NSThread: 0x600000063500>{number = 1, name = main}
 */

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

  • 使用(同步,異步)

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

//同步
-(void)sendNotification{

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

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

再看下通知隊列跟線程的了關系,把點擊屏幕直接發送通知,改成開啟一個線程發送通知

-(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] 異步發送通知before:<NSThread: 0x600000079480>{number = 3, name = (null)}
     2017-08-30 15:51:17.255 通知的底層解析[4244:137397] 異步發送通知after:<NSThread: 0x600000079480>{number = 3, name = (null)}
     */
}

看打印結果,發現根本沒有處理通知
再改下代碼,用線程發送同步的通知中心

-(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] 發送通知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] 發送通知after:<NSThread: 0x60000026bc40>{number = 3, name = (null)}
*/

而用線程發送同步的是可以接受到通知的,并且處理也是在線程里處理的
,這說通知隊列跟線程是有關系的,再繼續改代碼,回到線程發送異步通知,只是把發送時機改成馬上發送

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

這又能處理通知,所以可以說NSPostNow就是同步,其實呢,[[NSNotificationCenter defaultCenter]通知中心這句代碼的意思就是:你在哪個線程里面就是獲取當前線程的通知隊列并且默認采用NSPostNow發送時機

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

那么通知隊列到底和線程有什么關系呢:每個線程都有一個通知隊列,當線程結束了,通知隊列就被釋放了,所以當前選擇發送時機為NSPostWhenIdle時也就是空閑的時候發送通知,通知隊列就已經釋放了,所以通知發送不出去了
如果線程不結束,就可以發送通知了,用runloop讓線程不結束

//異步
-(void)sendAsyncNotification{

    //每個線程都默認又一個通知隊列,可以直接獲取,也可以alloc
    NSNotificationQueue * notificationQueue = [NSNotificationQueue defaultQueue];
    
    NSNotification * notification = [NSNotification notificationWithName:@"EdisonNotif" object:nil];
    
   
    NSLog(@"異步發送通知before:%@",[NSThread currentThread]);
    [notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
    NSLog(@"異步發送通知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] 異步發送通知before:<NSThread: 0x600000070200>{number = 3, name = (null)}
2017-08-30 16:12:55.098 通知的底層解析[4394:153794] 異步發送通知after:<NSThread: 0x600000070200>{number = 3, name = (null)}
2017-08-30 16:12:55.099 通知的底層解析[4394:153794] 接收到通知了:<NSThread: 0x600000070200>{number = 3, name = (null)}

*/

這樣通知就被發送出去了,而且發送和處理也在線程中,這還沒有達到真正的異步是吧,應該發送在一個線程,處理在另一個線程

再來看下消息合并

//異步
-(void)sendAsyncNotification{

    //每個線程都默認又一個通知隊列,可以直接獲取,也可以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,//按發送者合并
     
     */
    NSLog(@"異步發送通知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(@"異步發送通知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] 異步發送通知before:<NSThread: 0x608000265640>{number = 8, name = (null)}
2017-08-30 16:22:16.154 通知的底層解析[4407:159094] 異步發送通知after:<NSThread: 0x608000265640>{number = 8, name = (null)}
2017-08-30 16:22:16.154 通知的底層解析[4407:159094] 接收到通知了:<NSThread: 0x608000265640>{number = 8, name = (null)}

*/

設置成NSNotificationCoalescingOnName按名稱合并,此時我連續發送三條,但是只處理了一次,
再繼續,上面代碼就只是把發送時機改成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] 異步發送通知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] 異步發送通知after:<NSThread: 0x600000269a40>{number = 3, name = (null)}
*/

結果就打印了處理了三次通知,這個應該好理解吧,就跟dispatch_sync原理一樣,就是得發送因為NSPostNow是同步的,所以發送第一條通知,得等處理完第一條通知,才跑發送第二條通知,這樣肯定就沒有合并消息一說了,因為這有點類似線程阻塞的意思,只有異步,就是三個發送通知全部跑完,在處理通知的時候看是否需要合并和怎么合并,再去處理

那么系統哪些消息是合并的

drawRect

  • 通知隊列(線程關系,消息合并等,系統哪些消息做了合并)

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

通知隊列也可以實現異步,但是真正的異步還是得通過port

NSPort,底層所有的消息觸發都是通過端口來進行操作的

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

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

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

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

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

    
    NSLog(@"port處理任務:%@",[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發送通知before:<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] port發送通知after:<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] port處理任務:<NSThread: 0x600000075440>{number = 1, name = main}
2017-08-30 17:09:28.715 通知的底層解析[8171:198651] =1212
*/

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

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

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

*/
}
  • 分析層次結構(通知中心)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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