美文网首页iOS面试iOS interview面试
iOS底层系列31 -- Notification的底层原理

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

作者: YanZi_33 | 来源:发表于2022-03-12 19:04 被阅读0次
    • 关于NSNotification通知的源码下载地址点击这里
    image.png
    • iOS中通知的使用步骤,主要分为两个步骤:
      • 第一步:在通知中心注册通知;
      • 第二步:通知中心调用post函数,发送通知,观察者接受到通知执行回调函数,实现如下:
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor redColor];
        //第一步:注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"YanZi" object:nil];
    }
    
    //接受到通知,执行回调
    - (void)receiveNotification:(NSNotification *)notification {
        NSString *str = notification.userInfo[@"data"];
        NSLog(@"%@",str);
    }
    
    //第二步:发送通知
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"YanZi" object:nil userInfo:@{@"data":@"yanzi"}];
    }
    @end
    
    注册通知部分
    • 调用addObserver:selector:name: object: 向通知中心NSNotificationCenter注册观察者,观察者接收到通知后执行任务的代码(selector)在 发送通知的线程 中执行
    • 调用addObserverForName:object: queue: usingBlock: 向通知中心NSNotificationCenter注册观察者,观察者接受到通知后执行任务的代码在 指定的操作队列 中执行
    • 上述两种方法,底层实现都会会创建一个Observation对象,Observation的结构体如下所示:
    typedef struct  Obs {
      id        observer;   //接受消息的对象
      SEL       selector;    //回调方法
      struct Obs    *next;      //下一个Obs的节点指针
      int       retained;  //引用计数
      struct NCTbl  *link;      /* Pointer back to chunk table  */
    } Observation;
    
    • Observation结构体内部有一个observer成员,即观察者也就是接受消息的对象;
    • addObserver:selector:name: object:的源码实现如下:
    - (void)addObserver:(id)observer selector:(SEL)selector name:(NSString*)name object:(id)object {
      Observation   *list;
      Observation   *o;
      GSIMapTable   m;
      GSIMapNode    n;
     
      //入参的异常检测......
      
      //保证线程安全
      lockNCTable(TABLE);
    
      o = obsNew(TABLE, selector, observer);
      //通知名称存在时
      if (name){
          //NAMED是一个哈希表 根据name 取出节点node
          n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
          if (n == 0){
             //节点为空 创建maptable
             m = mapNew(TABLE);
             name = [name copyWithZone: NSDefaultMallocZone()];
             //将 maptable与name 以键值对的形式 存入NAMED哈希表中
             GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
             GS_CONSUMED(name)
          }else{
             m = (GSIMapTable)n->value.ptr;
          }
          //以object为key 在maptable哈希表中获取指定节点
          n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n == 0){
             o->next = ENDOBS;
             //若节点为空 将object与Observation 以键值对的形式 存入maptable哈希表中
             GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
          }else{
             //若节点存在 将Observer添加到Observation单链表中
             list = (Observation*)n->value.ptr;
             o->next = list->next;
             list->next = o;
          }
      }else if (object){
          //name为空 以object为key 从NAMELESS哈希表中取出 节点
          n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n == 0){
              o->next = ENDOBS;
              //节点不存在 以object与observation为键值对 存入NAMELESS哈希表中
              GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
          }else{
              //节点存在 将将Observer添加到Observation单链表中
              list = (Observation*)n->value.ptr;
              o->next = list->next;
              list->next = o;
          }
      }else{
          //当name与object都不存在的情况下 将Observation添加到WILDCARD单链表中
          o->next = WILDCARD;
          WILDCARD = o;
      }
      unlockNCTable(TABLE);
    }
    
    • 在阐述执行逻辑之间,首先介绍observer对象是怎么进行存储的;
    • 首先有一个named哈希表,当传入的通知name有值时,observer观察者最终会存储这个named的哈希表中,其结构如下所示:
      image.png
    • 可以看到name通知名称,其与maptable组成键值对存储到named哈希表中,而maptable也是一个哈希表,其存储的是object与observation的键值对,observation可以看成是observer观察者;
    • 其次还有一个nameless哈希表,当传入的通知名称为空时,observer观察者最终会存储在nameless哈希表中,其结构如下:
      image.png
    • 添加通知观察者的基本逻辑如下:
    • 首先,根据入参selector和observer封装成一个Observation对象;
    • 其次判断通知名称name是否存在;
      • 若通知名称name存在时,首先将name与maptable以键值对的形式添加到named哈希表中,然后将Observation与object以键值对的形式添加到maptable哈希表中;
      • 若通知名称name不存在,object存在时,最终将Observation与object以键值对的形式添加到nameless哈希表中;
      • 若通知名称name不存在,object也不存在时,将Observation添加到WILDCARD链表中;
    • 上述的逻辑关系见下图所示:
    image.png
    发送通知部分
    • 调用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];
    }
    
    • 内部调用_postAndRelease:函数,实现如下:
    - (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);
      //当name与object均不存在时,遍历WILDCARD链表中的observation对象 添加到数组a中
      for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next){
          GSIArrayAddItem(a, (GSIArrayItem)o);
      }
      //当name不存在,但object存在时,遍历NAMELESS哈希表,将所有observation对象 添加到数组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;
          }
         }
       }
      //当name存在时 遍历NAMED哈希表,将所有observation对象 添加到数组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数组 获取所有Observation中的observe对象 然后通过调用performSelector: 让观察者去调用selector方法(通知回调方法)
      count = GSIArrayCount(a);
      while (count-- > 0){
          o = GSIArrayItemAtIndex(a, count).ext;
          if (o->next != 0){
              NS_DURING{
                  //观察者去调用selector方法(通知回调方法)
                  [o->observer performSelector: o->selector withObject: notification];
              }
              //...
           }
      }
      lockNCTable(TABLE);
      GSIArrayEmpty(a);
      unlockNCTable(TABLE);
      RELEASE(notification);
    }
    
    • WILDCARD链表中named哈希表中nameless哈希表中获取Observation对象存储到数组GSIArray中;
    • 然后遍历GSIArray数组,取出observer对象,执行selector通知回调方法;
    面试题一:针对addObserver方法,当name为nil,object不为nil时,能否执行通知回调,若name与object都为nil时,发送通知时会发生什么?
    • 首先name为nil,object不为nil,Observation会被存储到nameless哈希表中,发送通知时会取出observer执行通知回调selector方法;
    • 其次name与object均为nil时,Observation会被存储到wildcard链表中,它会监听所有通知的回调
    面试题二:NSNotification发送是同步的还是异步的?如何实现异步发送通知?
    • 所谓通知的同步是指:通知中心发送通知后 需要等待观察者处理完成消息后 再继续执行下面的逻辑;
    • NSNotification发送默认是同步的,代码实现如下:
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //第一步:注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
    }
    
    //接受到通知,执行回调
    - (void)receiveNotification:(NSNotification *)notification {
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"收到通知");
        sleep(3);
    }
    
    //第二步:发送通知
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:@{@"data":@"发送通知"}];
        NSLog(@"通知发送完毕");
    }
    @end
    
    • 控制台执行结果如下:
    image.png
    • 实现异步发送通知,方式一:让通知回调方法在子线程中执行,实现如下:
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //第一步:注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
    }
    
    //接受到通知,执行回调
    - (void)receiveNotification:(NSNotification *)notification {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"%@",[NSThread currentThread]);
            NSLog(@"收到通知");
            sleep(3);
        });
    }
    
    //第二步:发送通知
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil userInfo:@{@"data":@"发送通知"}];
        NSLog(@"通知发送完毕");
        NSLog(@"%@",[NSThread currentThread]);
    }
    @end
    
    • 控制台调试结果如下:
    image.png
    • 实现异步发送通知,方式二:可以通过NSNotificationQueueenqueueNotification: postingStyle:enqueueNotification: postingStyle: coalesceMask: forModes:方法,将通知放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象,实现如下:
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //第一步:注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
    }
    
    //接受到通知,执行回调
    - (void)receiveNotification:(NSNotification *)notification {
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"收到通知");
        sleep(3);
    }
    
    //第二步:发送通知
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
        NSLog(@"通知发送完毕");
        NSLog(@"%@",[NSThread currentThread]);
    }
    @end
    
    • 控制台调试结果如下:
    image.png
    面试题三:NSNotificationQueue与RunLoop之间的关系?
    • NSNotificationQueue需依赖RunLoop才能成功触发通知 若子线程中不创建RunLoop是无法触发通知回调的,代码实现如下:
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //第一步:注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"111" object:nil];
    }
    
    //接受到通知,执行回调
    - (void)receiveNotification:(NSNotification *)notification {
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"收到通知");
        sleep(3);
    }
    
    //第二步:发送通知
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //NSNotificationQueue依赖RunLoop才能成功触发通知 否则接收不到回调
        //子线程的runLoop需主动获取
        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
    
    面试题四:页面销毁时不移除通知会崩溃么?多次添加同一个通知会怎样?多次移除同一个通知会怎样?
    • 页面销毁时不移除通知,在iOS9之前会导致崩溃,在iOS9之后不会导致崩溃,weak指针;
    • 多次添加同一个通知,由于底层源码未作过滤处理,那么发送通知时会触发多次回调;
    • 多次移除同一个通知,不会有什么影响;

    相关文章

      网友评论

        本文标题:iOS底层系列31 -- Notification的底层原理

        本文链接:https://www.haomeiwen.com/subject/cqakdrtx.html