美文网首页面试
『ios』NSNotification 实现原理

『ios』NSNotification 实现原理

作者: butterflyer | 来源:发表于2021-08-08 17:31 被阅读0次

    昨天跟一个六年没见的朋友在一起吃饭,他跟我分享了他这六年来的努力过程,深深的感受到自己的不足,同时也充满了动力。加油吧!

    NSNotification我们经常再用,就下面这几个api,我们也会看的很熟悉,包含了添加注册通知,发送通知,移除通知。

    + (NSNotificationCenter*) defaultCenter;
    
    - (void) addObserver: (id)observer
                selector: (SEL)selector
                    name: (NSString*)name
                  object: (id)object;
    #if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST)
    - (id) addObserverForName: (NSString *)name 
                       object: (id)object 
                        queue: (NSOperationQueue *)queue 
                   usingBlock: (GSNotificationBlock)block;
    #endif
    
    - (void) removeObserver: (id)observer;
    - (void) removeObserver: (id)observer
                       name: (NSString*)name
                     object: (id)object;
    
    - (void) postNotification: (NSNotification*)notification;
    - (void) postNotificationName: (NSString*)name
                           object: (id)object;
    - (void) postNotificationName: (NSString*)name
                           object: (id)object
                         userInfo: (NSDictionary*)info;
    

    那么,他是如何实现的?内部结构又是怎么样的。

    在分析源码之前,我们需要先熟悉下下面的几个结构体。

    //观察者
    typedef struct  Obs {
      id        observer;   /* Object to receive message.   */
      SEL       selector;   /* Method selector.     */
      struct Obs    *next;      /* Next item in linked list.    */
      int       retained;   /* Retain count for structure.  */
      struct NCTbl  *link;      /* Pointer back to chunk table  */
    } Observation;
    //内部的表
    typedef struct NCTbl {
      Observation       *wildcard;  /* Get ALL messages.        */
      GSIMapTable       nameless;   /* Get messages for any name.   */
      GSIMapTable       named;      /* Getting named messages only. */
      unsigned      lockCount;  /* Count recursive operations.  */
      NSRecursiveLock   *_lock;     /* Lock out other threads.  */
      Observation       *freeList;
      Observation       **chunks;
      unsigned      numChunks;
      GSIMapTable       cache[CACHESIZE];
      unsigned short    chunkIndex;
      unsigned short    cacheIndex;
    } NCTable;
    
    typedef struct _GSIMapTable GSIMapTable_t;
    typedef GSIMapTable_t *GSIMapTable;
    //存放链表的表
    struct  _GSIMapTable {
      NSZone    *zone;
      uintptr_t nodeCount;  /* Number of used nodes in map. */
      uintptr_t bucketCount;    /* Number of buckets in map.    */
      GSIMapBucket  buckets;    /* Array of buckets.        */
      GSIMapNode    freeNodes;  /* List of unused nodes.    */
      uintptr_t chunkCount; /* Number of chunks in array.   */
      GSIMapNode    *nodeChunks;    /* Chunks of allocated memory.  */
      uintptr_t increment;
    #ifdef  GSI_MAP_EXTRA
      GSI_MAP_EXTRA extra;
    #endif
    };
    
    typedef GSIMapBucket_t *GSIMapBucket;
    typedef GSIMapNode_t *GSIMapNode;
    #endif
    //节点
    struct  _GSIMapNode {
      GSIMapNode    nextInBucket;   /* Linked list of bucket.   */
      GSIMapKey key;
    #if GSI_MAP_HAS_VALUE
      GSIMapVal value;
    #endif
    };
    //链表
    struct  _GSIMapBucket {
      uintptr_t nodeCount;  /* Number of nodes in bucket.   */
      GSIMapNode    firstNode;  /* The linked list of nodes.    */
    };
    
    

    其实我们通过平时用addObserver的时候,我们肯定大致可以猜到,他肯定是一个根据name来把我们要观察的对象存起来了,但是这里我们只猜对了一部分。
    - (void) addObserver: (id)observer
    selector: (SEL)selector
    name: (NSString
    )name
    object: (id)object; 来看下他的源码
    *

    - (void) addObserver: (id)observer
            selector: (SEL)selector
                    name: (NSString*)name
              object: (id)object
    {
      Observation   *list;
      Observation   *o;
      GSIMapTable   m;
      GSIMapNode    n;
    
      if (observer == nil) //observer不能为nil
        [NSException raise: NSInvalidArgumentException
            format: @"Nil observer passed to addObserver ..."];
    
      if (selector == 0) //selector也不能为nil
        [NSException raise: NSInvalidArgumentException
            format: @"Null selector passed to addObserver ..."];
    
      if ([observer respondsToSelector: selector] == NO)//observer必须可以相应selector
        {
          [NSException raise: NSInvalidArgumentException
            format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
            NSStringFromClass([self class]), NSStringFromSelector(_cmd),
            observer, NSStringFromSelector(selector)];
        }
    
      lockNCTable(TABLE); //这里加锁是通过递归锁
    
      o = obsNew(TABLE, selector, observer);//
    
      if (name)
        {
          /*
           * Locate the map table for this name - create it if not present.
           */
          n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
          if (n == 0)
        {
          m = mapNew(TABLE);
          /*
           * As this is the first observation for the given name, we take a
           * copy of the name so it cannot be mutated while in the map.
           */
          name = [name copyWithZone: NSDefaultMallocZone()];
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          GS_CONSUMED(name)
        }
          else
        {
          m = (GSIMapTable)n->value.ptr;
        }
    
          /*
           * Add the observation to the list for the correct object.
           */
          n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n == 0)
        {
          o->next = ENDOBS;
          GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
        }
          else
        {
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
        }
        }
      else if (object)
        {
          n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n == 0)
        {
          o->next = ENDOBS;
          GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
        }
          else
        {
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
        }
        }
      else
        {
          o->next = WILDCARD;
          WILDCARD = o;
        }
    
      unlockNCTable(TABLE);
    }
    

    其实从这里我们大致可以看到,他做了下面的这几件事。

    1.首先会根据传入的参数实例化一个Observation,Observation对象保存了观察者对象,接收到通知观察者所执行的方法,以及下一个Observation对象的地址。
    2.根据是否传入NotificationName选择操作Named Table还是Nameless Table。
    3.若传入了NotificationName,则会以NotificationName为key去查找对应的Value,若找到value,则取出对应的value;若未找到对应的value,则新建一个table,然后将这个table以NotificationName为key添加到Named Table中。
    4.若在保存Observation的table中,以object为key取对应的链表。若找到了则直接在链接末尾插入之前实例化好的Observation;若未找到则以之前实例化好的Observation对象作为头节点插入进去。
    没有传入NotificationName的情况和上面的过程类似,只不过是直接根据对应的object为key去找对应的链表而已。如果既没有传入NotificationName也没有传入object,则这个观察者会添加到wildcard链表中。
    

    那么Named Table的结构图应该是下面的这个样子

    image.png
    在named table中,NotificationName作为表的key,但因注册观察者的时可传入一个object参数用于接收指定对象发出的通知,并且一个通知可注册多个观察者,所以还需要一张表来保存object和observer的对应关系。这张表以object为key,observer为value。
    外层是一个table,以通知名称NotificationName为key,其value为一个table(简称内层table)。
    内层table以object为key,其value为一个链表,用来保存所有的观察者。
    

    **如果NotificationName为nil,那么就叫Nameless Table **


    image.png

    可以看到Nameless没有最外层的name,通过object对应着不同的链表。
    那么NSNotification的结构搞清除了,我们继续往下看,post和remove方法。

    - (void) postNotification: (NSNotification*)notification
    {
      if (notification == nil)
        {
          [NSException raise: NSInvalidArgumentException
              format: @"Tried to post a nil notification."];
        }
      [self _postAndRelease: RETAIN(notification)];
    }
    
    /**
     * Creates and posts a notification using the
     * -postNotificationName:object:userInfo: passing a nil user info argument.
     */
    - (void) postNotificationName: (NSString*)name
                   object: (id)object
    {
      [self postNotificationName: name object: object userInfo: nil];
    }
    
    - (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;
    
      if (name == nil)
        {
          RELEASE(notification);
          [NSException raise: NSInvalidArgumentException
              format: @"Tried to post a notification with no name."];
        }
      object = [notification object];
    
      /*
       * Lock the table of observations while we traverse it.
       *
       * The table of observations contains weak pointers which are zeroed when
       * the observers get garbage collected.  So to avoid consistency problems
       * we disable gc while we copy all the observations we are interested in.
       * We use scanned memory in the array in the case where there are more
       * than the 64 observers we allowed room for on the stack.
       */
      GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
      lockNCTable(TABLE);
    
      /*
       * Find all the observers that specified neither NAME nor OBJECT.
       */
      for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
        {
          GSIArrayAddItem(a, (GSIArrayItem)o);
        }
    
      /*
       * Find the observers that specified OBJECT, but didn't specify NAME.
       */
      if (object)
        {
          n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n != 0)
        {
          o = purgeCollectedFromMapNode(NAMELESS, n);
          while (o != ENDOBS)
            {
              GSIArrayAddItem(a, (GSIArrayItem)o);
              o = o->next;
            }
        }
        }
    
      /*
       * Find the observers of NAME, except those observers with a non-nil OBJECT
       * that doesn't match the notification's OBJECT).
       */
      if (name)
        {
          n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
          if (n)
        {
          m = (GSIMapTable)n->value.ptr;
        }
          else
        {
          m = 0;
        }
          if (m != 0)
        {
          /*
           * First, observers with a matching object.
           */
          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)
            {
              /*
               * Now observers with a nil object.
               */
              n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
              if (n != 0)
            {
                  o = purgeCollectedFromMapNode(m, n);
              while (o != ENDOBS)
                {
                  GSIArrayAddItem(a, (GSIArrayItem)o);
                  o = o->next;
                }
            }
            }
        }
        }
    
      /* Finished with the table ... we can unlock it,
       */
      unlockNCTable(TABLE);
    
      /*
       * Now send all the notifications.
       */
      count = GSIArrayCount(a);
      while (count-- > 0)
        {
          o = GSIArrayItemAtIndex(a, count).ext;
          if (o->next != 0)
        {
              NS_DURING
                {
                  [o->observer performSelector: o->selector
                                    withObject: notification];
                }
              NS_HANDLER
                {
              BOOL  logged;
    
              /* Try to report the notification along with the exception,
               * but if there's a problem with the notification itself,
               * we just log the exception.
               */
              NS_DURING
            NSLog(@"Problem posting %@: %@", notification, localException);
            logged = YES;
              NS_HANDLER
            logged = NO;
              NS_ENDHANDLER
              if (NO == logged)
            { 
              NSLog(@"Problem posting notification: %@", localException);
            }  
                }
              NS_ENDHANDLER
        }
        }
      lockNCTable(TABLE);
      GSIArrayEmpty(a);
      unlockNCTable(TABLE);
    
      RELEASE(notification);
    }
    
    

    上面的post方法大概做了如下几个步骤

    1.首先会创建一个数组observerArray用来保存需要通知的observer。
    2.遍历wildcard链表,将observer添加到observerArray数组中。
    3.若存在object,在nameless table中找到以object为key的链表,然后遍历找到的链表,将observer添加到observerArray数组中。
    4.若存在NotificationName,在named table中以NotificationName为key找到对应的table,然后再在找到的table中以object为key找到对应的链表,遍历链表,将observer添加到observerArray数组中。如果object不为nil,则以nil为key找到对应的链表,遍历链表,将observer添加到observerArray数组中。
    5.至此所有关于当前通知的observer(wildcard+nameless+named)都已经加入到了数组observerArray中。遍历observerArray数组,取出其中的observer节点(包含了观察者对象和selector),调用形式如下:
    [o->observer performSelector: o->selector withObject: notification];
    这里我们可以看出,发送通知的线程和接收通知的线程是同一线程
    

    remove的底层实现

    - (void) removeObserver: (id)observer
               name: (NSString*)name
                     object: (id)object
    {
      if (name == nil && object == nil && observer == nil)
          return;
    
      /*
       *    NB. The removal algorithm depends on an implementation characteristic
       *    of our map tables - while enumerating a table, it is safe to remove
       *    the entry returned by the enumerator.
       */
    
      lockNCTable(TABLE);
    
      if (name == nil && object == nil)
        {
          WILDCARD = listPurge(WILDCARD, observer);
        }
    
      if (name == nil)
        {
          GSIMapEnumerator_t    e0;
          GSIMapNode        n0;
    
          /*
           * First try removing all named items set for this object.
           */
          e0 = GSIMapEnumeratorForMap(NAMED);
          n0 = GSIMapEnumeratorNextNode(&e0);
          while (n0 != 0)
        {
          GSIMapTable       m = (GSIMapTable)n0->value.ptr;
          NSString      *thisName = (NSString*)n0->key.obj;
    
          n0 = GSIMapEnumeratorNextNode(&e0);
          if (object == nil)
            {
              GSIMapEnumerator_t    e1 = GSIMapEnumeratorForMap(m);
              GSIMapNode        n1 = GSIMapEnumeratorNextNode(&e1);
    
              /*
               * Nil object and nil name, so we step through all the maps
               * keyed under the current name and remove all the objects
               * that match the observer.
               */
              while (n1 != 0)
            {
              GSIMapNode    next = GSIMapEnumeratorNextNode(&e1);
    
              purgeMapNode(m, n1, observer);
              n1 = next;
            }
            }
          else
            {
              GSIMapNode    n1;
    
              /*
               * Nil name, but non-nil object - we locate the map for the
               * specified object, and remove all the items that match
               * the observer.
               */
              n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
              if (n1 != 0)
            {
              purgeMapNode(m, n1, observer);
            }
            }
          /*
           * If we removed all the observations keyed under this name, we
           * must remove the map table too.
           */
          if (m->nodeCount == 0)
            {
              mapFree(TABLE, m);
              GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
            }
        }
    
          /*
           * Now remove unnamed items
           */
          if (object == nil)
        {
          e0 = GSIMapEnumeratorForMap(NAMELESS);
          n0 = GSIMapEnumeratorNextNode(&e0);
          while (n0 != 0)
            {
              GSIMapNode    next = GSIMapEnumeratorNextNode(&e0);
    
              purgeMapNode(NAMELESS, n0, observer);
              n0 = next;
            }
        }
          else
        {
          n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n0 != 0)
            {
              purgeMapNode(NAMELESS, n0, observer);
            }
        }
        }
      else
        {
          GSIMapTable       m;
          GSIMapEnumerator_t    e0;
          GSIMapNode        n0;
    
          /*
           * Locate the map table for this name.
           */
          n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
          if (n0 == 0)
        {
          unlockNCTable(TABLE);
          return;       /* Nothing to do.   */
        }
          m = (GSIMapTable)n0->value.ptr;
    
          if (object == nil)
        {
          e0 = GSIMapEnumeratorForMap(m);
          n0 = GSIMapEnumeratorNextNode(&e0);
    
          while (n0 != 0)
            {
              GSIMapNode    next = GSIMapEnumeratorNextNode(&e0);
    
              purgeMapNode(m, n0, observer);
              n0 = next;
            }
        }
          else
        {
          n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n0 != 0)
            {
              purgeMapNode(m, n0, observer);
            }
        }
          if (m->nodeCount == 0)
        {
          mapFree(TABLE, m);
          GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
        }
        }
      unlockNCTable(TABLE);
    }
    
    /**
     * Deregisters observer from all notifications.  This should be called before
     * the observer is deallocated.
    */
    - (void) removeObserver: (id)observer
    {
      if (observer == nil)
        return;
    
      [self removeObserver: observer name: nil object: nil];
    }
    
    

    大概做了如下几个步骤

    1.若NotificationName和object都为nil,则清空wildcard链表。
    2.若NotificationName为nil,遍历named table,若object为nil,则清空named table,若object不为nil,则以object为key找到对应的链表,然后清空链表。在nameless table中以object为key找到对应的observer链表,然后清空,若object也为nil,则清空nameless table。
    3.若NotificationName不为nil,在named table中以NotificationName为key找到对应的table,若object为nil,则清空找到的table,若object不为nil,则以object为key在找到的table中取出对应的链表,然后清空链表。
    
    

    相关文章

      网友评论

        本文标题:『ios』NSNotification 实现原理

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