美文网首页面试
『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