Files
knowledge-kit/Chapter1 - iOS/1.104.md
2024-07-15 20:03:01 +08:00

36 KiB
Raw Blame History

NSNotification底层原理

有人聊起来 NSNotification 可以在不同的线程发和接收吗?对于不知道或者不确定的知识,有必要探究记录下

NSNotificationCenter

@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
// 添加 Observer
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 发送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// 移除通知
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 添加 Observer
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@end

通过官方 API 可以窥探得到,系统内部应该是维护了 NSNotificationName、Observer、selector、object 之间的关系。

直接上 GUNStep 源码探索下

Observation

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;

结构体存储了 observer、selector 信息。此外可以看出是一个链表结构next指向注册了同一个通知的下一个观察者。

NCTbl

typedef struct NCTbl {
  Observation        *wildcard;    /* Get ALL messages.        */
  GSIMapTable        nameless;    /* Get messages for any name.    */
  GSIMapTable        named;        /* Getting named messages only.    */
  // ...
} NCTable;

查看 NCTbl 结构体定义发现其内部存在2张 MapTable。

  • named用于保存添加观察者的时候传入 NotificationName 的情况

  • nemeless同于保存添加观察者时没有传递 NotificationName 的情况

named Table

该表用于存储添加观察者时传了 NotificationName 的情况。也就是 Named Table 中NotificationName 作为 key。在使用系统 API 注册观察者的时候还可以传入 object 参数,表示只监听该对象发出的通知,所以还需要一张表存储 object 和 observer 的对应关系object 为 keyobserver 为 value。

  • 第一个 MapTable key 为 notificationNamevalue 为另一个 MapTable子 Table

  • 子 Table 以 object 为 keyvalue 为链表,存储所有的观察者

  • object 为 nil 的时候,系统会根据 nil 自动生成一个 key相当于这个 key 对应的值链表保存的就是当前通知传入了 NotificationName 且没有 object 的所有观察者。

nameless Table

nameless Table 结构较为简单,因为没有 notificationName所以就一层 MapTable。key 为 objectvalue 为链表,存储所有的观察者。

wildcard

wildcard 也是链表结构。如果在添加 Observer 的时候没有传递 notificationName、object 则会将 Observer 添加到 wildcard 链表中,注册到这里的观察者可以响应所有的系统通知。

添加通知

添加通知的所有 API最终都会调用该 API。

- (void)addObserver:(id)observer
           selector:(SEL)selector
               name:(NSString *)name
             object:(id)object {
    Observation *list;
    Observation *o;
    GSIMapTable m;
    GSIMapNode n;
    // 参数合法性校验
    if (observer == nil)
      [NSException raise:NSInvalidArgumentException
                  format:@"Nil observer passed to addObserver ..."];

    if (selector == 0)
      [NSException raise:NSInvalidArgumentException
                  format:@"Null selector passed to addObserver ..."];

    if ([observer respondsToSelector:selector] == NO)
    {
      [NSException raise:NSInvalidArgumentException
                  format:@"[%@-%@] Observer '%@' does not respond to selector '%@'",
                        NSStringFromClass([self class]), NSStringFromSelector(_cmd),
                        observer, NSStringFromSelector(selector)];
    }

    lockNCTable(TABLE);
    // 调用 obsNew 方法,创建 observation 对象,持有 SEL、object
    o = obsNew(TABLE, selector, observer);

    /*
    * Record the Observation in one of the linked lists.
    *
    * NB. It is possible to register an observer for a notification more than
    * once - in which case, the observer will receive multiple messages when
    * the notification is posted... odd, but the MacOS-X docs specify this.
    */
    // notificationName 存在的逻辑
    if (name) {
      /*
      * Locate the map table for this name - create it if not present.
      */
      // NAMED 是一个宏定义,表示名为 named 的字典key 为 name从 named 表中获取对应的 mapTable
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      // 不存在,则创建
      if (n == 0) { 
        m = mapNew(TABLE); // 先从缓存中取,如果没有则新建 mapTable
        /*
        * 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 {
        // 存在 mapTable 则取出值也就是 named MapTable
        m = (GSIMapTable)n->value.ptr;
      }

      /*
      * Add the observation to the list for the correct object.
      */
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      // 从 named MapTable 中以 key 为 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) {
      // 走到这里代表 name 为空,但 object 不为空。此时从 nameless MapTable 中以 object 获取链表对象值。
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      // 不存在则创建新的链表,并存入 nameless MapTable
      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 {
// name、object 都为空,则将添加的 observation 观察者添加到 WILDCARD 链表
      o->next = WILDCARD;
      WILDCARD = o;
    }
    unlockNCTable(TABLE);
}

static Observation *obsNew(NCTable *t, SEL s, id o)
{
  Observation *obs;
  if (t->freeList == 0)
  {
    Observation *block;

    if (t->chunkIndex == CHUNKSIZE)
    {
      unsigned size;

      t->numChunks++;

      size = t->numChunks * sizeof(Observation *);
      t->chunks = (Observation **)NSReallocateCollectable(
          t->chunks, size, NSScannedOption);

      size = CHUNKSIZE * sizeof(Observation);
      t->chunks[t->numChunks - 1] = (Observation *)NSAllocateCollectable(size, 0);
      t->chunkIndex = 0;
    }
    block = t->chunks[t->numChunks - 1];
    t->freeList = &block[t->chunkIndex];
    t->chunkIndex++;
    t->freeList->link = 0;
  }
  obs = t->freeList;
  t->freeList = (Observation *)obs->link;
  obs->link = (void *)t;
  obs->retained = 0;
  obs->next = 0;
  // 持有 observer 和 selector
  obs->selector = s;
  obs->observer = o;
  return obs;
}

通过源码,我们可以发现和设想差不多,得出以下结论:

  • 在调用 (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;方法后,系统内部会调用 obsNew 方法,创建 Observation 对象(内部结构是一个结构体),内部保存了观察者 Observer、接收通知时会执行的方法 selector

  • 如果传递了 notificationName 则会去 Named MapTable 中,以 notificationName 为 key查找对应的 value子 MapTable。如果不存在则创建一个新的 MapTable

    • 在子 MapTable 中,以 object 为 key去取对应的 Observation 链表。如果没有 object 则会生成一个默认的 key表示所有的通知都会被监听

    • 通过 object 生成的 key 查找 Observation 链表,如果不存在,则创建一个节点,并作为头节点。如果有链表,则将 Observation 插入尾部(链表的基础操作)

  • 如果 notificationName 不存在,且 object 不为空,则通过 object 生成的 key查找 Observation 链表,如果没有则创建一个节点,且作为头节点,如果有链表,则插入到尾部

  • 如果 notificationName、object 都为空,则直接把创建的 Observation 对象存储在 wildcard 链表结构中

发送通知

postNotification 相关的 API 最终都会调用到 _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;
  // 参数合法性判断
  if (name == nil) {
      RELEASE(notification);
      [NSException raise: NSInvalidArgumentException
          format: @"Tried to post a notification with no name."];
  }
  object = [notification object];
  // 创建 Array 来存储 Observation
  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);
  // 遍历 WILDCARD 链表,将其中的 Observation 对象都添加到 Array 中的 既没有 notificationName又没有 object
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next) {
      GSIArrayAddItem(a, (GSIArrayItem)o);
  }
  // 拿到 object在 nameless 表中,以 object 为 key查找对应的链表
  if (object) {
    n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
    // 链表存在,则遍历链表,将 Observation 对象添加到 Array 中
    if (n != 0){
      o = purgeCollectedFromMapNode(NAMELESS, n);
      while (o != ENDOBS) {
        GSIArrayAddItem(a, (GSIArrayItem)o);
        o = o->next;
      }
    }
  }

  // 如果 NotificationName 存在
  if (name) {
      //则以 NotificationName 为 key在 Named MapTable 中寻找对应的子 MapTable
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n) {
          m = (GSIMapTable)n->value.ptr;
        } else {
          m = 0;
        }
      if (m != 0) {
        n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
        // 当子 MapTable 存在的时候,以 object 为 key获取对应的链表
        if (n != 0) {
            o = purgeCollectedFromMapNode(m, n);
            // 当链表存在的时候,遍历链表,将其中的 Observation 对象添加到数组
            while (o != ENDOBS) {
              GSIArrayAddItem(a, (GSIArrayItem)o);
              o = o->next;
            }
        }

        if (object != nil) {
          // 以 nil 为 object key查找对应的 Observation添加到数组中
          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);

  // 最后遍历数组,调用 Observation 结构体中的 selector 方法。
  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);
  // 释放 NSNotification 对象
  RELEASE(notification);
}

通过源码分析,得出以下结论:

  • 调用 -(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject 的时候,内部会生成一个 Array 来存储 Observation 信息

  • 先将 WILDCARD 中的所有 Observation 添加到 Array 中去通过分析发送通知源码知道WILDCARD 中 Observation 都是没有 Object、NotificationName 的,也就是可以接收所有的通知

  • 然后在 NAMELESS MapTable 中,以 Object 为 key获取对应的链表遍历链表将其中的 Observation 添加到数组中

  • 在 NAMED MapTable 中,先以 NotificationName 为 key获取对应的子 MapTable

    • 在子 MapTable 中根据 object 为 key获取对应的链表。遍历链表将其中的 Observation 对象添加到数组

    • 如果 object 不为 nil则以 nil 为 object key查找对应的 Observation添加到 Array 中

  • 最后遍历 Array调用 Observation 结构体成员变量 observer 的 selector 方法。以 performSelector: withObject: 形式调用的,所以可以看出都是在同一个线程同步执行的

  • 释放 NSNootification 对象

移除通知

// 遍历 Observation 链表,判断节点的 observer 等于传递来的参数,则删除该节点
static Observation *listPurge(Observation *list, id observer)
{
  Observation    *tmp;

  while (list != ENDOBS && list->observer == observer)
    {
      tmp = list->next;
      list->next = 0;
      obsFree(list);
      list = tmp;
    }
  if (list != ENDOBS)
    {
      tmp = list;
      while (tmp->next != ENDOBS)
    {
      if (tmp->next->observer == observer)
        {
          Observation    *next = tmp->next;

          tmp->next = next->next;
          next->next = 0;
          obsFree(next);
        }
      else
        {
          tmp = tmp->next;
        }
    }
    }
  return list;
}

- (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);
  // NotificationName、object 都为 nil则说明被加入到了 WILDCARD 链表中,调用 listPurge 方法,遍历链表,删除节点中 observer 等于换入参数的节点
  if (name == nil && object == nil)
    {
      WILDCARD = listPurge(WILDCARD, observer);
    }
  // NotficationName 为空
  if (name == nil)
    {
      GSIMapEnumerator_t    e0;
      GSIMapNode        n0;

      /*
       * First try removing all named items set for this object.
       */
      e0 = GSIMapEnumeratorForMap(NAMED);
      n0 = GSIMapEnumeratorNextNode(&e0);
      // 现在 NAMED MapTable 中遍历所有子 MapTable
      while (n0 != 0)
    {
      GSIMapTable        m = (GSIMapTable)n0->value.ptr;
      NSString        *thisName = (NSString*)n0->key.obj;

      n0 = GSIMapEnumeratorNextNode(&e0);
      // object 为空,则遍历 MapTable 中的节点
      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);
          // 清空与 observer 相同的节点
          purgeMapNode(m, n1, observer);
          n1 = next;
        }
        }
      else
        // 如果 object 不为空
        {
          GSIMapNode    n1;
          // 则以 object 为 key在子 MapTable 中找到链表,清空链表中所有与 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);
        }
    }

     // 处理 NAMELESS MapTable 
      if (object == nil)
    {
      e0 = GSIMapEnumeratorForMap(NAMELESS);
      n0 = GSIMapEnumeratorNextNode(&e0);
      // object 为空,则遍历链表,删除与 observer 相同的节点
      while (n0 != 0)
        {
          GSIMapNode    next = GSIMapEnumeratorNextNode(&e0);

          purgeMapNode(NAMELESS, n0, observer);
          n0 = next;
        }
    }
      else
    {
    // 如果 object 不为空,则根据 object 从 NAMELESS MapTable 中以 object 为 key找到对应的链表
      n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n0 != 0)
        {
          // 删除与 Observer 相同的节点
          purgeMapNode(NAMELESS, n0, observer);
        }
    }
    }
  else
    // NotificationName 不为空美好
    {
      GSIMapTable        m;
      GSIMapEnumerator_t    e0;
      GSIMapNode        n0;

      // 则从 NAMED MapTable 中以 name 为 key获取对应的子 MapTable
      n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n0 == 0)
    {
      unlockNCTable(TABLE);
      return;        /* Nothing to do.    */
    }
      m = (GSIMapTable)n0->value.ptr;
        // object 为空,则在子 MapTable 中删除节点(节点值与 observer 相同)
      if (object == nil)
    {
      e0 = GSIMapEnumeratorForMap(m);
      n0 = GSIMapEnumeratorNextNode(&e0);

      while (n0 != 0)
        {
          GSIMapNode    next = GSIMapEnumeratorNextNode(&e0);

          purgeMapNode(m, n0, observer);
          n0 = next;
        }
    }
      else
    {
     // 如果 object 不为空,则以 object 为 key取出对应的链表然后将链表中与 observer 相同的节点删除
      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);
}

查看源码,得出以下结论:

  • 调用删除通知观察者最后都会收敛到该API (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

  • 内部会先判断 NotificationName、object 如果都是 nil则移除 WILDCARD 链表中与 observer 相同的节点

  • 如果 NotificationName 为 nil

    • 先在 NAMED MapTable 中遍历子 MapTable

      • 如果 object 为 nil则遍历子 MapTable且删除与 observer 相同的节点

      • object 不为 nil则以 object 为 key获取子 MapTable 中对应的链表,然后清空链表中与 observer 相同的节点

    • 再处理 NAMLESS MapTable

      • 如果 object 为 nil则直接遍历并删除 table 中所有与 observer 相同的节点

      • 如果 object 不为 nil则以 object 为 key获取对应的链表遍历并删除 table 中所有与 observer 相同的节点

  • 如果 NotificationName 不为 nil则在 NAMED MapTable 中以 NotificationName 为 key获取链表

    • 如果 object 为 nil则遍历 MapTable 中所有的节点,清空与 observer 相同的节点

    • 如果 object 不为 nil则以 object 为 key获取子 MapTable 中对应的链表,然后清空链表中与 observer 相同的节点

如何异步发送通知

这个 case 就需引入 NSNotificationQueue 也就是通知队列了。

简单来说 NSNotificationQueue 是 NSNotificationCenter 的缓冲池。当我们调用 -(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject 的这种方式的时候就是同步发送通知。通知会直接发送到 NSNotificationCenter然后 NSNotificationCenter 会直接将其发送给注册了该通知的观察者。

使用 NSNotificationQueue ,则通知不是直接发送给 NSNotificationCenter而是先存储在 NSNotificationQueue 中,然后再由 notification 转发给注册的观察者。

且可以实现合并相同 NSNotificationName 的通知功。NSNotificationQueue 遵循队列先进先出的特性FIFO当一个通知处于对头的时候它会被发送给 NSNotificationCenter然后 NSNotificationCenter 再将该 notiication 转发给注册了该通知的所以监听者。

每一个线程都有一个默认的 NSNotificationQueue该队列与通知中心联系在一起。也可以为一个线程创建多个 NSNotificationQueue。

其所有 API

@interface NSNotificationQueue : NSObject {
@private
    id        _notificationCenter;
    id        _asapQueue;
    id        _asapObs;
    id        _idleQueue;
    id        _idleObs;
}
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
// 创建
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;
// 添加观察者(入队)
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;
// 移除观察者(出队)
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
@end

其中 postingStyle 是枚举参数

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,
    NSPostASAP = 2,
    NSPostNow = 3
};
  • NSPostWhenIdle代表在空闲时发送 notification 到 NSNotificationCenter。也就是本线程 RunLoop 空闲时即发送通知到通知中心

  • NSPostASAPas soon as possible尽可能快。即当前通知或者 timer 回调执行结束就发送通知到通知中心,还是需要依赖 RunLoop

  • NSPostNow马上发送

postingStyle 也是枚举

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,
    NSNotificationCoalescingOnName = 1,
    NSNotificationCoalescingOnSender = 2
};
  • NSNotificationNoCoalescing不管是否 NSNotificationName 是否重复,都不合并

  • NSNotificationCoalescingOnNameNSNotificationName 相同的多个 NSNotification 会被合并为一个。

  • NSNotificationCoalescingOnSender按照发送方如果多个通知发送方相同则保留一个

测试异步发送通知

- (void)mockNotificationQueue
{
    //每个进程默认有一个通知队列,默认是没有开启的,底层通过队列实现,队列维护一个调度表
    NSNotification *notifi = [NSNotification notificationWithName:@"Notification" object:nil];
    NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
    //FIFO
    NSLog(@"notifi before");
    [queue enqueueNotification:notifi postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode, nil]];
    NSLog(@"notifi after");
    NSPort *port = [[NSPort alloc] init];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"runloop over");
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotifi:) name:@"Notification" object:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self mockNotificationQueue];
}
- (void)handleNotifi:(NSNotification *)notification
{
    NSLog(@"%@", [NSThread currentThread]);
}
// NSPostWhenIdle、NSPostASAP
2022-05-07 01:18:01.859643+0800 DDD[62783:2383065] notifi before
2022-05-07 01:18:01.859924+0800 DDD[62783:2383065] notifi after
2022-05-07 01:18:01.860887+0800 DDD[62783:2383065] <_NSMainThread: 0x600003530840>{number = 1, name = main}
2022-05-07 01:18:02.005840+0800 DDD[62783:2383065] notifi before
2022-05-07 01:18:02.006072+0800 DDD[62783:2383065] notifi after
2022-05-07 01:18:02.006882+0800 DDD[62783:2383065] <_NSMainThread: 0x600003530840>{number = 1, name = main}
// NSPostNow
2022-05-07 01:35:21.387512+0800 DDD[63186:2401325] notifi before
2022-05-07 01:35:21.387748+0800 DDD[63186:2401325] <_NSMainThread: 0x60000315c880>{number = 1, name = main}
2022-05-07 01:35:21.387917+0800 DDD[63186:2401325] notifi after
2022-05-07 01:35:21.532892+0800 DDD[63186:2401325] notifi before
2022-05-07 01:35:21.533130+0800 DDD[63186:2401325] <_NSMainThread: 0x60000315c880>{number = 1, name = main}
2022-05-07 01:35:21.533292+0800 DDD[63186:2401325] notifi after

改变参数发现:

NSPostWhenIdle异步

NSPostASAP 异步

NSPostNow 同步

所以要实现异步发送通知,则必须使用 NSNotificationQueue 相关 APIpostingStyle 参数必须为 NSPostASAP、NSPostWhenIdle不能为 NSPostNow、不能为 NSPostNow、不能为 NSPostNow。

通知和 RunLoop 的关系

- (void)notifiWithRunloop
{
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        if(activity == kCFRunLoopEntry){
            NSLog(@"进入 Runloop");
        }else if(activity == kCFRunLoopBeforeWaiting){
            NSLog(@"即将进入等待状态");
        }else if(activity == kCFRunLoopAfterWaiting){
            NSLog(@"结束等待状态");
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    CFRelease(observer);

    NSNotification *notification1 = [NSNotification notificationWithName:@"notify" object:nil userInfo:@{@"key":@"1"}];
    NSNotification *notification2 = [NSNotification notificationWithName:@"notify" object:nil userInfo:@{@"key":@"2"}];
    NSNotification *notification3 = [NSNotification notificationWithName:@"notify" object:nil userInfo:@{@"key":@"3"}];

    [[NSNotificationQueue defaultQueue] enqueueNotification:notification1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];

    [[NSNotificationQueue defaultQueue] enqueueNotification:notification2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];

    [[NSNotificationQueue defaultQueue] enqueueNotification:notification3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];

    NSPort *port = [[NSPort alloc] init];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotifi:) name:@"notify" object:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self notifiWithRunloop];
}

- (void)handleNotifi:(NSNotification *)notification
{
    NSLog(@"%@", notification.userInfo);
}
2022-05-07 01:43:51.470047+0800 DDD[63370:2409134] {
    key = 3;
}
2022-05-07 01:43:51.470312+0800 DDD[63370:2409134] 进入Runloop
2022-05-07 01:43:51.470522+0800 DDD[63370:2409134] {
    key = 2;
}
2022-05-07 01:43:51.470962+0800 DDD[63370:2409134] 进入Runloop
2022-05-07 01:43:51.471223+0800 DDD[63370:2409134] 即将进入等待状态
2022-05-07 01:43:51.471422+0800 DDD[63370:2409134] {
    key = 1;
}
2022-05-07 01:43:51.471613+0800 DDD[63370:2409134] 结束等待状态
2022-05-07 01:43:51.471759+0800 DDD[63370:2409134] 即将进入等待状态
2022-05-07 01:43:51.479267+0800 DDD[63370:2409134] 结束等待状态
2022-05-07 01:43:51.480009+0800 DDD[63370:2409134] 进入Runloop
2022-05-07 01:43:51.480172+0800 DDD[63370:2409134] 即将进入等待状态
2022-05-07 01:43:51.842003+0800 DDD[63370:2409134] 结束等待状态
2022-05-07 01:43:51.842938+0800 DDD[63370:2409134] 即将进入等待状态
2022-05-07 01:44:33.109154+0800 DDD[63370:2409134] 结束等待状态

通过 Demo 和对应的打印可以看出NSNotificationQueue 相关的 API 和 RunLoop 有关系,当 postingStyle 参数为 NSPostNow 的时候则说明通知没有进入 RunLoop而是直接立即执行。参数为 NSPostASAP、NSPostWhenIdle 的时候都和 RunLoop 有关NSPostASAP 通知快于 NSPostWhenIdle。

通知重定向

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"dispatch thread = %@", [NSThread currentThread]);
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TESTNOTIFICATION" object:nil];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"TESTNOTIFICATION" object:nil userInfo:nil];
    });
}
- (void)handleNotification:(NSNotification *)notification
{
    NSLog(@"receive thread = %@", [NSThread currentThread]);
}

2022-05-07 01:55:21.042542+0800 DDD[63607:2419849] dispatch thread = <_NSMainThread: 0x600001b44840>{number = 1, name = main}
2022-05-07 01:55:21.042835+0800 DDD[63607:2419937] receive thread = <NSThread: 0x600001b1c9c0>{number = 5, name = (null)}

虽然我们在主线程中注册了通知的观察者,但在全局队列中 postNotification 并不是在主线程处理的。如果我们想在回调中处理与 UI 相关的操作,需要确保是在主线程中执行回调。

为什么不直接在处理通知事件的地方强制切回主线程?

不推荐。假如子线程发送多个通知,注册多个不同的观察者,那你是否要在每一个通知处理的地方都去切主线程,不够收口

For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.

这里谈到重定向。一种重定向的实现思路是自定义一个通知队列注意不是NSNotificationQueue 对象而是一个数组让这个队列去维护那些我们需要重定向的Notification。我们仍然是像平常一样去注册一个通知的观察者当 Notification 来了时,判断 postNotification 的线程是不是所期望的线程,如果不是,则将这个 Notification 存储到我们的队列中,并发送一个信号 signal 到期望的线程中来告诉这个线程需要处理一个Notification。指定的线程在收到信号后将 Notification 从队列中移除,并进行处理。

官方 Demo 如下

@interface ViewController () <NSMachPortDelegate>
@property (nonatomic) NSMutableArray    *notifications;         // 通知队列
@property (nonatomic) NSThread          *notificationThread;    // 期望线程
@property (nonatomic) NSLock            *notificationLock;      // 用于对通知队列加锁的锁对象,避免线程冲突
@property (nonatomic) NSMachPort        *notificationPort;      // 用于向期望线程发送信号的通信端口

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"current thread = %@", [NSThread currentThread]);

    // 初始化
    self.notifications = [[NSMutableArray alloc] init];
    self.notificationLock = [[NSLock alloc] init];

    self.notificationThread = [NSThread currentThread];
    self.notificationPort = [[NSMachPort alloc] init];
    self.notificationPort.delegate = self;

    // 往当前线程的run loop添加端口源
    // 当Mach消息到达而接收线程的run loop没有运行时则内核会保存这条消息直到下一次进入run loop
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                forMode:(__bridge NSString *)kCFRunLoopCommonModes];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];

    });
}

- (void)handleMachMessage:(void *)msg {
    [self.notificationLock lock];
    while ([self.notifications count]) {
        NSNotification *notification = [self.notifications objectAtIndex:0];
        [self.notifications removeObjectAtIndex:0];
        [self.notificationLock unlock];
        [self processNotification:notification];
        [self.notificationLock lock];
    };
    [self.notificationLock unlock];
}

- (void)processNotification:(NSNotification *)notification {
    if ([NSThread currentThread] != _notificationThread) {
        // Forward the notification to the correct thread.
        [self.notificationLock lock];
        [self.notifications addObject:notification];
        [self.notificationLock unlock];
        [self.notificationPort sendBeforeDate:[NSDate date]
                                   components:nil
                                         from:nil
                                     reserved:0];
    }
    else {
        // Process the notification here;
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"process notification");
    }
}
@end

这种方式先将当前线程存储下来,在收到通知的时候去遍历当前数组(数组代替队列),判断当前线程是不是目标线程,不是则将对头元素移动到对尾。

FAQ

通知的发送是同步还是异步?

通过 postNotification 源码可以看到,通知的发送是同步的,且在同一个线程中

页面销毁时,不移除通知会 crash 吗?

通过这段文档,我们可以看出

  • 使用 addObserverForName:object:queue:usingBlock 必须自己手动移除
  • 使用 addObserver:selector:name:object: ios9后系统会自动移除

如何自动移除 ios9以后系统使用weak指针修饰observe,当observe被释放后再次发送消息给nil发送不会引起崩溃并且根据描述中提到系统会下次发送通知时移除这些oboserve为nil的观察者

多次添加同一个通知的观察者会出现什么问题?多次移除同一个通知会有问题吗?

查看 addObserver 源码会发现,针对同一个 NSNotificationName 进行多次添加,系统并不会过滤,假设有 object则会维护 NAMED MapTablekey 为 NSNotificationNamevalue 为子 MapTable子 MapTable 中 object 为 keyvalue 为 observer。所以多次添加则会造成当 postNotification 的时候会有多次响应。

查看 removeObserver 源码发现,移除都会针对 NSNotificationName 进行操作,从 NAMED MapTable 中,以 NSNofiticationName 为 key获取 value 为子 MapTable 。子 MapTable 根据 object 为 key获取对应的链表然后根据参数 observer 移除链表中所有 observer 都为传递的 observer 的节点。所以多次调用不会存在问题。