NotificationCenter是一个体系组件,它担任协谐和办理事件的告诉和呼应。它的基本原理是根据观察者形式!而 Apple 对其是闭源的,因而无法检查 NotificationCenter 的源码,可是能够经过剖析开源的 Swift 来了解 NotificationCenter 的完成,以下是一个简化的完成:

简略完成

1、首先界说一个NotificationCenter类界说


class RYNotificationCenter {
  private init(){}
  static let `default` = RYNotificationCenter()
  private var observers: [RYNotificationObserver] = []
}

界说了一个单例,用于在整个程序中共享,observers数组用来存储已经注册的一切观察者。

2、然后界说一个观察者目标

观察者目标用来封装具体的观察者的信息。


class RYNotificationObserver {
  var name: String
  var block: (Notification) -> Void
  init(name: String, block: @escaping (Notification) -> Void) {
    self.name = name
    self.block = block
  }
}

3、在NotificationCenter中增加注册观察者的办法


func addObserver(name: String, block: @escaping (Notification) -> Void) -> RYNotificationObserver {
  let observer = RYNotificationObserver(name: name, block: block)
  observers.append(observer)
  return observer
}

addObserver办法用于注册观察者。在这个完成中,咱们创建了一个新 RYNotificationObserver 目标并将其增加到 observers 数组。这个办法回来观察者目标,以便稍后从 NotificationCenter 中移除。

4、在 NotificationCenter 中增加发送告诉的办法


/// 发送告诉的本质是利用了观察者形式
/// 让观察者数组履行闭包中的代码
func post(name: String, userInfo: [AnyHashable: Any]? = nil) {
    let notification = Notification(name: Notification.Name(name), userInfo: userInfo)
  observers
    .filter({ $0.name == name })
    .forEach { $0.block(notification) }
}

post 办法用来发送告诉,它承受告诉名以及可选的userInfo字典。一起参数都包装在Notification目标中,然后遍历 observers 数组。假如观察者的称号和告诉称号匹配,咱们将履行保存的block

5、在NotificationCenter中增加移除告诉者的办法

func removeObserver(_ observer: RYNotificationObserver) {
  if let index = observers.firstIndex(where: { $0 === observer }) {
    observers.remove(at: index)
  }
}

removeObserver 办法用于移除观察者。它承受一个观察者目标并从 observers 数组中移除它。

NotificationCenter的源码剖析

普遍来说,现在剖析 NotificationCenter 的源码,一般是 github.com/gnustep/lib… ,这是在 gnustep 库的源码中,它和官方的具体完成肯定是有差异的,可是能够以它为参考的目标,在这里告诉的源码运用了三个首要的类:

  • NSNotification

  • NSNotificationCenter

  • NSNotificationQueue

NSNotificationCenter 完成

用于在观察者和发送者之间发送告诉,这是中心类,它的办法和Objective-C是一致的,运用 **addObserver:selector:name:object: 办法来增加观察者,可是它在内部运用了C言语完成链表的数据结构Obs存储观察者相关的信息:

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;

而在 postNotificationName:object:userInfo: 办法履行的时分会经过告诉名找到封装好的 Obs 观察者,然后履行相应的办法:


- (void) postNotificationName: (NSString*)name
   object: (id)object
  userInfo: (NSDictionary*)info
{
 // 先封装好notification
 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];
}
// 然后调用观察者的selector办法
- (void) _postAndRealse: (NSNotification*)notification {
......
[o->observer performSelector: o->selector withObject: notification];
......
}

当然,要将封装好的 notification ,作为参数传递给观察者需要履行的 selector

NSNotification 完成

那么 Notifiation 呢?它是一个包含了告诉的称号、发送者目标以及用户信息字典的不可变目标。


- (id) initWithCoder: (NSCoder*)aCoder
{
 NSString *name;
 id object;
 NSDictionary *info;
 id n;
 [aCoder decodeValueOfObjCType: @encode(id) at: &name];
 [aCoder decodeValueOfObjCType: @encode(id) at: &object];
 [aCoder decodeValueOfObjCType: @encode(id) at: &info];
 n = [NSNotification notificationWithName: name object: object userInfo: info];
 RELEASE(name);
 RELEASE(object);
 RELEASE(info);
 DESTROY(self);
 return RETAIN(n);
}

NSNotificationQueue 的完成

最后是 NSNotificationQueue 的完成,它是一个用于办理告诉发送的行列,能够依照特定的发送形式(例如合并相同的告诉或按发送顺序)将告诉排队。


- (void) enqueueNotification: (NSNotification*)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes
{
    if (modes == nil)
    {
      modes = defaultMode;
    }
    if (coalesceMask != NSNotificationNoCoalescing)
  {
   [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask];
  }
    switch (postingStyle) {
        case NSPostNow: {
            NSString *mode;
            mode = [[NSRunLoop currentRunLoop] currentMode];
            if (mode == nil || [modes indexOfObject: mode] != NSNotFound)
            {
                [_center postNotification: notification];
            }
        }
        break;
   case NSPostASAP:
            add_to_queue(_asapQueue, notification, modes, _zone);
            break;
   case NSPostWhenIdle:
            add_to_queue(_idleQueue, notification, modes, _zone);
            break;
  }
}

当运用 NSNotificationQueue 的时分,就不需要咱们手动发送 Notification 了,NSNotificationQueue 会自动帮咱们发送,在上述代码中,假如是 NSPostNow,那么告诉会立马被发送,否则就先加入行列中:_asapQueue 或许 _idleQueue ,然后在合适的时分履行行列中的告诉,比如:


void GSPrivateNotifyIdle(NSString *mode) {
    NotificationQueueList *item;
    for (item = currentList(); item; item = item->next)
    {
        if (item->queue) {
            notify(item->queue->_center,
                   item->queue->_idleQueue,
                   mode,
                   item->queue->_zone);
        }
    }
}

问题:假如NotificationCenter 增加的观察者是self,会形成循环引证吗?

答案是:不会!

NotificationCenter 对观察者的引证方式是弱引证(weak),而不是强持有(strong)。因而,当一个目标被销毁时,它的 deinit 办法会被调用,即便它是一个观察者。所以即便咱们不在 deinit 办法中增加移除 self 的操作也是能够的,由于 NotificationCenter 并没有对观察者强持有。

问题:假如 NotificationCenter 增加的是 block ,而 block 强持有了 self ,这会形成循环引证吗?

答案是:会!

iOS 9开始,假如运用了根据 block 的观察者,那么就需要去当心观察者的生命周期了,由于NotificationCenter 对增加的 block 是强持有的,正如上述简略完成中的那样,它对闭包中捕获的变量就也是强持有的,所以为了防止这种现象,需要保证运用 [weak self] 来捕获列表。

在实际运用的时分,由于编码惯性,或许会在 deinit 办法中移除根据 block 的观察者以处理该问题:


class ViewController: UIViewController {
  private weak var observer: NSObjectProtocol!
    func addObserver() {
        observer = NotificationCenter.default.addObserver(forName: NSNotification.Name("test"), object: nil, queue: OperationQueue.main) { _ in
            self.view.backgroundColor = UIColor.white
    }
  }
    deinit {
        NotificationCenter.default.removeObserver(observer!)
  }
}

可是在这种情况下, deinit 办法并不会履行! 原因便是 NotificationCenter 持有了 block, 也间接持有了 self,而 NotificationCenter 是一个单例,所以这种持有联系是一直存在的,导致了 deinit 办法并不会履行!

问题:观察者的 selector 履行的线程和发送告诉的线程有关吗?

答案是:正相关!

从上文中的简略完成以及GNU的源码中基本能够看出结论了。增加观察者的线程并没有什么影响,而发送告诉的线程,其实便是调用办法履行的线程,所以两者是在同一线程履行的。


func addObserver() {
  NotificationCenter.default.addObserver(self, selector: #selector(click), name: NSNotification.Name.init("test"), object: nil)
  DispatchQueue.global().async {
    NotificationCenter.default.post(name: NSNotification.Name.init("test"), object: nil)
    NSLog("curretThread1: \(Thread.current)")
  }
}
@objc func click() {
  NSLog("curretThread2: \(Thread.current)")
}
// curretThread2: <NSThread: 0x600001358240>{number = 6, name = (null)}
// curretThread1: <NSThread: 0x600001358240>{number = 6, name = (null)}

一起还需要注意的便是告诉发送,然后 selector 被履行,这个过程其实本质上是一个观察者形式的完成方式,一起,它也是同步履行的,再履行完发送消息的办法后就会去寻找对应的 Observer ,找到之后就履行相应的 selector ,履行完之后,发送消息的办法才履行结束了。

所以发送告诉和监听告诉履行办法的中心是:相同线程履行 且 同步履行。