NSNotificationCenter底层探究

文章目录

  • 简单介绍及使用
    • swift版使用
    • ObjectiveC版使用
  • 底层
    • NSNotificationCenter - 消息中心
    • named表
    • nameless表
    • wildcard链表
    • 添加观察者
    • 发送通知
    • 移除通知
  • 参考文献

简单介绍及使用

通知,简单来说就是,一方使用name发送通知,持有与之相同name的另一方接收通知并进行处理(如果添加的观察者name为空,那么就会接收所有通知)。特点是基本上不用考虑其它影响因素,只需要使用同样的通知名称,监听该通知的对象(即观察者)再对通知做出反应即可。降低耦合度

swift版使用

// 添加观察者 这个方法一定要是@objc
// 可以发现 这个方法的name为可选值,可以为nil
NotificationCenter.default.addObserver(self, selector: #selector(test), name: NSNotification.Name.init("test"), object: nil)
// 发送通知
// name不是可选值,必须得有名字
NotificationCenter.default.post(name: NSNotification.Name.init("test"), object: nil, userInfo: nil)

ObjectiveC版使用

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

从使用代码中可以看出来,NSNotificationCenter是一个单例

底层

NSNotificationCenter - 消息中心

GNUStep的源码
创建源码:

static NSNotificationCenter *default_center = nil;

+ (NSNotificationCenter*) defaultCenter
{
  return default_center;
}

NSNotificationCenter内部主要定义了两个表和一个链表,还有一个对象结构体

typedef struct NCTbl {
  Observation       *wildcard;  /* Get ALL messages. */
  GSIMapTable       nameless;   /* Get messages for any name. */
  GSIMapTable       named;      /* Getting named messages only. */
} NCTable
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;

还有一些宏定义,下面会用到

#define        TABLE                ((NCTable*)_table)
#define        WILDCARD             (TABLE->wildcard)
#define        NAMELESS             (TABLE->nameless)
#define        NAMED                (TABLE->named)
#define        LOCKCOUNT            (TABLE->lockCount)
  1. named
    添加观察者时传入了 NotificationName 的表
  2. nameless
    添加观察者时没有传入 NotificationName 的表
  3. wildcard
    添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上

named表

named表是一个嵌套表,外层以 NotificationName 为key,在注册观察者时可以传入一个object参数,所以内层是以 object 为 key,Observation 为 value,又因为可以多个对象观察同一个对象,所以Observation 是一个链表,这一整个key-value表作为外层的value,大概图长这个样子:
NSNotificationCenter底层探究_第1张图片
PS:我们在实际开发中,经常会将object设为nil,这时系统会根据nil自动生成一个key,之后所有object为nil的观察者都会使用这个key,换句话说,这个key对应了所有object为nil的观察者

nameless表

有了named表,nameless表应该不难想象,因为没有 NotificationName ,所以少了named表外层的key,将value单独拎出来就组成了named表。也就是,以object为key,Observation为value,结构图为:
NSNotificationCenter底层探究_第2张图片

wildcard链表

named表是有 NotificationName 的表,nameless是没有 NotificationName 只有 object 的表,那么wildcard 当然就是兜底啦,既没有 NotificationName 也没有 object 的观察者就都存放在这里,也就是只有 Observation 这么一个值了,用链表连接在一起即可。在这里的观察者会接收到所有通知
NSNotificationCenter底层探究_第3张图片

添加观察者

使用方法addObserver:selector:name:object
根据GNUStep上的源码一步步分析:

- (void) addObserver: (id)observer
            selector: (SEL)selector
                name: (NSString*)name
              object: (id)object
{
  Observation        *list;
  Observation        *o;
  GSIMapTable        m;
  GSIMapNode        n;
// observer为空时的报错
  if (observer == nil)
    [NSException raise: NSInvalidArgumentException
                format: @"Nil observer passed to addObserver ..."];
// selector为空时的报
  if (selector == 0)
    [NSException raise: NSInvalidArgumentException
                format: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错
  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);
// 建立一个新Observation
  o = obsNew(TABLE, selector, observer);
// 如果有name
  if (name) {
  // 在named表中 以name为key寻找value
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      // named表中没有value
      if (n == 0) {
          // 新建一个表
          m = mapNew(TABLE);
          // 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)
          name = [name copyWithZone: NSDefaultMallocZone()];
          // 新建表作为name的value添加在named表中
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          GS_CONSUMED(name)
      } else {
      // 取出对应的value
          m = (GSIMapTable)n->value.ptr;
      }
      // 将observation添加到正确object的列表中
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      // n是object的value
      if (n == 0) {
          o->next = ENDOBS;
          // 将o作为object的value链表的头结点插入
          GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
      } else {
          // 在链表尾部加入o
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
      // 这个else if 就是没用name有object的 Observation,对object进行的操作相同,
  } 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 {
 // 既没有name又没有object,就加在WILDCARD链表中
      o->next = WILDCARD;
      WILDCARD = o;
 }
// 解锁
  unlockNCTable(TABLE);
}

总结

  1. 首先会根据传入的observer,selector创建一个Observation
  2. 查看是否存在name
    2.1 存在:以name为key去找对应的value,如果有,则取出value;如果没有,新建一个table,以name为key存进named表中。
    2.2 不存在:直接进入步骤3
  3. 在object表中,寻找以传入的object为key的链表,如果有,在尾部加入之前实例化好的Observation;如果没有,就将Observation实例作为头结点插入object的value链表
  4. 如果既没有name又没有object,就加在WILDCARD链表中

发送通知

使用方法postNotification:postNotificationName:object:userInfo或者postNotificationName:object:,后者默认userInfo为nil
同样使用GNUStep源码进行分析

- (void) postNotification: (NSNotification*)notification {
  if (notification == nil) {
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a nil notification."];
    }
  [self _postAndRelease: RETAIN(notification)];
}

- (void) postNotificationName: (NSString*)name
                       object: (id)object {
  [self postNotificationName: name object: object userInfo: nil];
}


- (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:方法。不同的是,postNotification:方法外部直接传了一个NSNotification对象,其他两个方法都是内部进行了处理包装 成为了一个NSNotification对象
再看看_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;
   // name为空的报错
  if (name == nil) {
      RELEASE(notification);
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a notification with no name."];
    }
  object = [notification object];

  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);
  // 查找所有未指定name或object的观察者,加在a数组中
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
    {
      GSIArrayAddItem(a, (GSIArrayItem)o);
    }
// 查找与通知的object相同但是没有name的观察者,加在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的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中
  if (name) {
  // 先匹配name
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n) {
      // 从匹配的name对应的value表中继续查找
          m = (GSIMapTable)n->value.ptr;
      } else {
          m = 0;
      }
      if (m != 0) {
          // 首先,查找与通知的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) {
          // 接着是没有object的观察者
              n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
              if (n != 0) {
                  o = purgeCollectedFromMapNode(m, n);
                  while (o != ENDOBS) {
                      GSIArrayAddItem(a, (GSIArrayItem)o);
                      o = o->next;
                    }
                }
            }
        }
    }
  unlockNCTable(TABLE);

  // 发送所有通知
  count = GSIArrayCount(a);
  while (count-- > 0) {
      o = GSIArrayItemAtIndex(a, count).ext;
      if (o->next != 0) {
          NS_DURING {
          // 给observer发送selector,让其处理
              [o->observer performSelector: o->selector
                                withObject: notification];
            }
          NS_HANDLER {
              BOOL        logged;
              // 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。
              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);
}

总结

  1. 首先创建一个observers数组存储需要发通知的observer(源码中为a数组)
  2. 先遍历wildcard链表,将所有observer添加到observers数组中
  3. 若存在object,在nameless表中查找与通知的object相同的观察者,加入observers数组中
  4. 若存在name,在named表中先查找name对应的table,再在table中查找与通知的object相同的观察者,加入observers数组中。如果 object 不 为nil,则再以 nil 为 key 找到对应的链表,遍历链表,将 observer 添加到 observers 数组中
  5. 最后再遍历observers数组,通知observer需要处理selector
    这种处理通知的方式也就能说明,发送通知的线程和接收通知的线程是同一线程

移除通知

注意 在iOS9之前需要手动调用removeObserver:方法进行释放,如果在对象释放后没有及时移除通知,在对象释放之后再发送通知可能会造成野指针crash;但是在iOS9之后,通知中心持有的观察者由unsafe_unretained引用变为weak引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被回收后自动置空。但是通过addObserverForName:object: queue:usingBlock:方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。如果使用 Block 的方式进行添加的,一定要持有对象,在释放的时候,需要自己手动进行释放操作,不然会出现对象释放了,但是发送通知的时候,block还是会被调用,此时 bolck 中的 self 对象已经是 nil 了
GNUStep源码:

- (void) removeObserver: (id)observer {
  if (observer == nil)
    return;

  [self removeObserver: observer name: nil object: nil];
}

- (void) removeObserver: (id)observer
                   name: (NSString*)name
                 object: (id)object {
  if (name == nil && object == nil && observer == nil)
      return;

  lockNCTable(TABLE);
  // name和object都为nil,就在wildcard链表里删除
  if (name == nil && object == nil) {
      WILDCARD = listPurge(WILDCARD, observer);
    }
   // name为空时
  if (name == nil) {
      GSIMapEnumerator_t        e0;
      GSIMapNode                n0;
       // 首先尝试删除为此object设置的所有命名项目
       // 在named表中
      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) {
          // 清空named表
              GSIMapEnumerator_t        e1 = GSIMapEnumeratorForMap(m);
              GSIMapNode                n1 = GSIMapEnumeratorNextNode(&e1);

              while (n1 != 0) {
                  GSIMapNode        next = GSIMapEnumeratorNextNode(&e1);

                  purgeMapNode(m, n1, observer);
                  n1 = next;
                }
          } else {
          // 以object为key找到对应链表,清空链表
              GSIMapNode        n1;
              n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
              if (n1 != 0) {
                  purgeMapNode(m, n1, observer);
                }
            }
          if (m->nodeCount == 0) {
              mapFree(TABLE, m);
              GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
            }
        }
        // 开始操作nameless表
      if (object == nil) {
      // 清空nameless表
          e0 = GSIMapEnumeratorForMap(NAMELESS);
          n0 = GSIMapEnumeratorNextNode(&e0);
          while (n0 != 0) {
              GSIMapNode        next = GSIMapEnumeratorNextNode(&e0);

              purgeMapNode(NAMELESS, n0, observer);
              n0 = next;
            }
        } else {
        // 找到对应的observer链表,清空
          n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n0 != 0) {
              purgeMapNode(NAMELESS, n0, observer);
            }
        }
   } else {
      GSIMapTable                m;
      GSIMapEnumerator_t        e0;
      GSIMapNode                n0;

      n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      // 如果没有和这个name相同的key,直接返回
      if (n0 == 0) {
          unlockNCTable(TABLE);
          return;                /* Nothing to do.        */
      }
      m = (GSIMapTable)n0->value.ptr;

      if (object == nil) {
      // 如果object为nil,就清空找到的table
          e0 = GSIMapEnumeratorForMap(m);
          n0 = GSIMapEnumeratorNextNode(&e0);

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

              purgeMapNode(m, n0, observer);
              n0 = next;
            }
      } else {
       // 清空object对应的链表
          n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n0 != 0) {
              purgeMapNode(m, n0, observer);
            }
        }
        // 清除named表中的作为key的name
      if (m->nodeCount == 0) {
          mapFree(TABLE, m);
          GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
        }
    }
  unlockNCTable(TABLE);
}

总结

  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中取出对应的链表,然后清空链表

参考文献

GNUStep源码
NSNotificationCenter使用总结
底层初窥——NSNotificationCenter

你可能感兴趣的:(iOS)