iOS底層系列31 -- Notification的底層原理

image.png
  • iOS中通知的使用步驟,主要分為兩個(gè)步驟:
    • 第一步:在通知中心注冊(cè)通知;
    • 第二步:通知中心調(diào)用post函數(shù),發(fā)送通知,觀察者接受到通知執(zhí)行回調(diào)函數(shù),實(shí)現(xiàn)如下:
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor redColor];
    //第一步:注冊(cè)通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"YanZi" object:nil];
}

//接受到通知,執(zhí)行回調(diào)
- (void)receiveNotification:(NSNotification *)notification {
    NSString *str = notification.userInfo[@"data"];
    NSLog(@"%@",str);
}

//第二步:發(fā)送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"YanZi" object:nil userInfo:@{@"data":@"yanzi"}];
}
@end
注冊(cè)通知部分
  • 調(diào)用addObserver:selector:name: object: 向通知中心NSNotificationCenter注冊(cè)觀察者,觀察者接收到通知后執(zhí)行任務(wù)的代碼(selector)在 發(fā)送通知的線程 中執(zhí)行;
  • 調(diào)用addObserverForName:object: queue: usingBlock: 向通知中心NSNotificationCenter注冊(cè)觀察者,觀察者接受到通知后執(zhí)行任務(wù)的代碼在 指定的操作隊(duì)列 中執(zhí)行;
  • 上述兩種方法,底層實(shí)現(xiàn)都會(huì)會(huì)創(chuàng)建一個(gè)Observation對(duì)象,Observation的結(jié)構(gòu)體如下所示:
typedef struct  Obs {
  id        observer;   //接受消息的對(duì)象
  SEL       selector;    //回調(diào)方法
  struct Obs    *next;      //下一個(gè)Obs的節(jié)點(diǎn)指針
  int       retained;  //引用計(jì)數(shù)
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;
  • Observation結(jié)構(gòu)體內(nèi)部有一個(gè)observer成員,即觀察者也就是接受消息的對(duì)象;
  • addObserver:selector:name: object:的源碼實(shí)現(xiàn)如下:
- (void)addObserver:(id)observer selector:(SEL)selector name:(NSString*)name object:(id)object {
  Observation   *list;
  Observation   *o;
  GSIMapTable   m;
  GSIMapNode    n;
 
  //入?yún)⒌漠惓z測(cè)......
  
  //保證線程安全
  lockNCTable(TABLE);

  o = obsNew(TABLE, selector, observer);
  //通知名稱(chēng)存在時(shí)
  if (name){
      //NAMED是一個(gè)哈希表 根據(jù)name 取出節(jié)點(diǎn)node
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      if (n == 0){
         //節(jié)點(diǎn)為空 創(chuàng)建maptable
         m = mapNew(TABLE);
         name = [name copyWithZone: NSDefaultMallocZone()];
         //將 maptable與name 以鍵值對(duì)的形式 存入NAMED哈希表中
         GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
         GS_CONSUMED(name)
      }else{
         m = (GSIMapTable)n->value.ptr;
      }
      //以object為key 在maptable哈希表中獲取指定節(jié)點(diǎn)
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      if (n == 0){
         o->next = ENDOBS;
         //若節(jié)點(diǎn)為空 將object與Observation 以鍵值對(duì)的形式 存入maptable哈希表中
         GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
      }else{
         //若節(jié)點(diǎn)存在 將Observer添加到Observation單鏈表中
         list = (Observation*)n->value.ptr;
         o->next = list->next;
         list->next = o;
      }
  }else if (object){
      //name為空 以object為key 從NAMELESS哈希表中取出 節(jié)點(diǎn)
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n == 0){
          o->next = ENDOBS;
          //節(jié)點(diǎn)不存在 以object與observation為鍵值對(duì) 存入NAMELESS哈希表中
          GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
      }else{
          //節(jié)點(diǎn)存在 將將Observer添加到Observation單鏈表中
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
  }else{
      //當(dāng)name與object都不存在的情況下 將Observation添加到WILDCARD單鏈表中
      o->next = WILDCARD;
      WILDCARD = o;
  }
  unlockNCTable(TABLE);
}
  • 在闡述執(zhí)行邏輯之間,首先介紹observer對(duì)象是怎么進(jìn)行存儲(chǔ)的;
  • 首先有一個(gè)named哈希表,當(dāng)傳入的通知name有值時(shí),observer觀察者最終會(huì)存儲(chǔ)這個(gè)named的哈希表中,其結(jié)構(gòu)如下所示:
    image.png
  • 可以看到name通知名稱(chēng),其與maptable組成鍵值對(duì)存儲(chǔ)到named哈希表中,而maptable也是一個(gè)哈希表,其存儲(chǔ)的是object與observation的鍵值對(duì),observation可以看成是observer觀察者;
  • 其次還有一個(gè)nameless哈希表,當(dāng)傳入的通知名稱(chēng)為空時(shí),observer觀察者最終會(huì)存儲(chǔ)在nameless哈希表中,其結(jié)構(gòu)如下:
    image.png
  • 添加通知觀察者的基本邏輯如下:
  • 首先,根據(jù)入?yún)elector和observer封裝成一個(gè)Observation對(duì)象;
  • 其次判斷通知名稱(chēng)name是否存在;
    • 若通知名稱(chēng)name存在時(shí),首先將name與maptable以鍵值對(duì)的形式添加到named哈希表中,然后將Observation與object以鍵值對(duì)的形式添加到maptable哈希表中;
    • 若通知名稱(chēng)name不存在,object存在時(shí),最終將Observation與object以鍵值對(duì)的形式添加到nameless哈希表中;
    • 若通知名稱(chēng)name不存在,object也不存在時(shí),將Observation添加到WILDCARD鏈表中;
  • 上述的邏輯關(guān)系見(jiàn)下圖所示:
image.png
發(fā)送通知部分
  • 調(diào)用postNotificationName: object:userInfo:方法,源碼如下:
- (void)postNotificationName:(NSString*)name object:(id)object userInfo:(NSDictionary*)info{
  GSNotification *notification;
  notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
  notification->_name = [name copyWithZone: [self zone]];
  notification->_object = [object retain];
  notification->_info = [info retain];
  [self _postAndRelease: notification];
}
  • 內(nèi)部調(diào)用_postAndRelease:函數(shù),實(shí)現(xiàn)如下:
- (void) _postAndRelease: (NSNotification*)notification{
  Observation   *o;
  unsigned  count;
  NSString  *name = [notification name];
  id        object;
  GSIMapNode    n;
  GSIMapTable   m;
  GSIArrayItem  i[64];
  GSIArray_t    b;
  GSIArray  a = &b;
  //...
  object = [notification object];
  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);
  //當(dāng)name與object均不存在時(shí),遍歷WILDCARD鏈表中的observation對(duì)象 添加到數(shù)組a中
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next){
      GSIArrayAddItem(a, (GSIArrayItem)o);
  }
  //當(dāng)name不存在,但object存在時(shí),遍歷NAMELESS哈希表,將所有observation對(duì)象 添加到數(shù)組a中
  if (object){
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n != 0){
      o = purgeCollectedFromMapNode(NAMELESS, n);
      while (o != ENDOBS){
          GSIArrayAddItem(a, (GSIArrayItem)o);
          o = o->next;
      }
     }
   }
  //當(dāng)name存在時(shí) 遍歷NAMED哈希表,將所有observation對(duì)象 添加到數(shù)組a中
  if (name){
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n){
         m = (GSIMapTable)n->value.ptr;
      }else{
         m = 0;
      }
      if (m != 0){
         n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
         if (n != 0){
          o = purgeCollectedFromMapNode(m, n);
          while (o != ENDOBS){
            GSIArrayAddItem(a, (GSIArrayItem)o);
            o = o->next;
          }
        }

        if (object != nil){
          n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
          if (n != 0){
              o = purgeCollectedFromMapNode(m, n);
              while (o != ENDOBS){
                GSIArrayAddItem(a, (GSIArrayItem)o);
                o = o->next;
            }
          }
        }
      }
  }
  unlockNCTable(TABLE);
  //遍歷a數(shù)組 獲取所有Observation中的observe對(duì)象 然后通過(guò)調(diào)用performSelector: 讓觀察者去調(diào)用selector方法(通知回調(diào)方法)
  count = GSIArrayCount(a);
  while (count-- > 0){
      o = GSIArrayItemAtIndex(a, count).ext;
      if (o->next != 0){
          NS_DURING{
              //觀察者去調(diào)用selector方法(通知回調(diào)方法)
              [o->observer performSelector: o->selector withObject: notification];
          }
          //...
       }
  }
  lockNCTable(TABLE);
  GSIArrayEmpty(a);
  unlockNCTable(TABLE);
  RELEASE(notification);
}
  • WILDCARD鏈表中,named哈希表中,nameless哈希表中獲取Observation對(duì)象存儲(chǔ)到數(shù)組GSIArray中;
  • 然后遍歷GSIArray數(shù)組,取出observer對(duì)象,執(zhí)行selector通知回調(diào)方法;
面試題一:針對(duì)addObserver方法,當(dāng)name為nil,object不為nil時(shí),能否執(zhí)行通知回調(diào),若name與object都為nil時(shí),發(fā)送通知時(shí)會(huì)發(fā)生什么?
  • 首先name為nil,object不為nil,Observation會(huì)被存儲(chǔ)到nameless哈希表中,發(fā)送通知時(shí)會(huì)取出observer執(zhí)行通知回調(diào)selector方法;
  • 其次name與object均為nil時(shí),Observation會(huì)被存儲(chǔ)到wildcard鏈表中,它會(huì)監(jiān)聽(tīng)所有通知的回調(diào);
面試題二:NSNotification發(fā)送是同步的還是異步的?如何實(shí)現(xiàn)異步發(fā)送通知?
  • 所謂通知的同步是指:通知中心發(fā)送通知后 需要等待觀察者處理完成消息后 再繼續(xù)執(zhí)行下面的邏輯;
  • NSNotification發(fā)送默認(rèn)是同步的,代碼實(shí)現(xiàn)如下:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //第一步:注冊(cè)通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
}

//接受到通知,執(zhí)行回調(diào)
- (void)receiveNotification:(NSNotification *)notification {
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"收到通知");
    sleep(3);
}

//第二步:發(fā)送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:@{@"data":@"發(fā)送通知"}];
    NSLog(@"通知發(fā)送完畢");
}
@end
  • 控制臺(tái)執(zhí)行結(jié)果如下:
image.png
  • 實(shí)現(xiàn)異步發(fā)送通知,方式一:讓通知回調(diào)方法在子線程中執(zhí)行,實(shí)現(xiàn)如下:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //第一步:注冊(cè)通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
}

//接受到通知,執(zhí)行回調(diào)
- (void)receiveNotification:(NSNotification *)notification {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"收到通知");
        sleep(3);
    });
}

//第二步:發(fā)送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:@{@"data":@"發(fā)送通知"}];
    NSLog(@"通知發(fā)送完畢");
    NSLog(@"%@",[NSThread currentThread]);
}
@end
  • 控制臺(tái)調(diào)試結(jié)果如下:
image.png
  • 實(shí)現(xiàn)異步發(fā)送通知,方式二:可以通過(guò)NSNotificationQueueenqueueNotification: postingStyle:enqueueNotification: postingStyle: coalesceMask: forModes:方法,將通知放入隊(duì)列,實(shí)現(xiàn)異步發(fā)送,在把通告放入隊(duì)列之后,這些方法會(huì)立即將控制權(quán)返回給調(diào)用對(duì)象,實(shí)現(xiàn)如下:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //第一步:注冊(cè)通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
}

//接受到通知,執(zhí)行回調(diào)
- (void)receiveNotification:(NSNotification *)notification {
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"收到通知");
    sleep(3);
}

//第二步:發(fā)送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
    NSLog(@"通知發(fā)送完畢");
    NSLog(@"%@",[NSThread currentThread]);
}
@end
  • 控制臺(tái)調(diào)試結(jié)果如下:
image.png
面試題三:NSNotificationQueue與RunLoop之間的關(guān)系?
  • NSNotificationQueue需依賴(lài)RunLoop才能成功觸發(fā)通知 若子線程中不創(chuàng)建RunLoop是無(wú)法觸發(fā)通知回調(diào)的,代碼實(shí)現(xiàn)如下:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //第一步:注冊(cè)通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
}

//接受到通知,執(zhí)行回調(diào)
- (void)receiveNotification:(NSNotification *)notification {
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"收到通知");
    sleep(3);
}

//第二步:發(fā)送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //NSNotificationQueue依賴(lài)RunLoop才能成功觸發(fā)通知 否則接收不到回調(diào)
    //子線程的runLoop需主動(dòng)獲取
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    });
}
@end
面試題四:頁(yè)面銷(xiāo)毀時(shí)不移除通知會(huì)崩潰么?多次添加同一個(gè)通知會(huì)怎樣?多次移除同一個(gè)通知會(huì)怎樣?
  • 頁(yè)面銷(xiāo)毀時(shí)不移除通知,在iOS9之前會(huì)導(dǎo)致崩潰,在iOS9之后不會(huì)導(dǎo)致崩潰,weak指針;
  • 多次添加同一個(gè)通知,由于底層源碼未作過(guò)濾處理,那么發(fā)送通知時(shí)會(huì)觸發(fā)多次回調(diào);
  • 多次移除同一個(gè)通知,不會(huì)有什么影響;
最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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