通知中心
大约 7 分钟
Notification
是观察者模式的一种实现,程序中一种一对多的通信方式。androind
类似的机制叫做Broadcast
广播。
NotificationCenter
- “通知中心”
首先思考一个问题: 如果让你来设计一个精简版通知中心你会怎么设计?
NotificationCenter
: 设计成为一个单例。addObserver
: 注册通知,常规的参数主要有两个通知的唯一标识名和通知执行的Selector
。那么可以很自然地想到使用哈希表来存储注册通知。由于同一个通知可以有多个观察者,就是说可以注册多次。所以哈希表的键可以使用通知名,执行回调应该放在一个数值中。post
: 发送通知时,通过发送的通知名,可以取出回调闭包的数组,然后遍历执行每个通知的Selector
。
通知中心的定义
// 通知中心定义
open class NotificationCenter : NSObject {
// 通知中心单例
open class var `default`: NotificationCenter { get }
// 注册通知
open func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)
@available(iOS 4.0, *)
open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol
// 发送通知
open func post(_ notification: Notification)
open func post(name aName: NSNotification.Name, object anObject: Any?)
open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil)
// 移除注册通知
open func removeObserver(_ observer: Any)
open func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?)
}
// 通知体定义
public struct Notification : ReferenceConvertible, Equatable, Hashable {
/// A tag identifying the notification.
public var name: Notification.Name // 通知的唯一标识
/// An object that the poster wishes to send to observers.
///
/// Typically this is the object that posted the notification.
public var object: Any? // 通知携带对象,发送者希望发送给观察者,一般来说这个对象就是发送者
/// Storage for values or objects related to this notification.
public var userInfo: [AnyHashable : Any]? // 存储与通知相关的值或者对象
}
// line:139 通知体结构
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;
// line:232 通知中心表结构
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;
// line:750 添加通知
- (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);
// 创建通知节点
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.
*/
// 遍历查找应该加入的链表
if (name) { // 1> 名字存在,首先通过名字查询
/*
* 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) { // 2> 通过通知对象 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 { // 3> 均不存在,加入通配符链表
o->next = WILDCARD;
WILDCARD = o;
}
unlockNCTable(TABLE);
}
GUNStep
源码实现中,通知体 Obs
是一个链表节点,存放通知方法 Selector
是通过链表来存储,不是通过数组。 通知中心表 NCTbl
中存放的通知主要分为三类
- named : 注册时有名字
- nameless : 注册时无名字,有对象
- wildcard : 通配符,注册时无名字,无对象
NotificationQueue
- “通知队列”
通知队列定义
// 通知队列
open class NotificationQueue : NSObject {
// 默认队列
open class var `default`: NotificationQueue { get }
// 初始化队列时与一个通知中心关联
public init(notificationCenter: NotificationCenter)
// 入队列
open func enqueue(_ notification: Notification, postingStyle: NotificationQueue.PostingStyle)
open func enqueue(_ notification: Notification, postingStyle: NotificationQueue.PostingStyle, coalesceMask: NotificationQueue.NotificationCoalescing, forModes modes: [RunLoop.Mode]?)
// 出队列
open func dequeueNotifications(matching notification: Notification, coalesceMask: Int)
// 通知的发送时机
public enum PostingStyle : UInt {
case whenIdle = 1 // runloop 空闲时发送
case asap = 2 // (As Soon As Possible)尽快发送,穿插在 runloop 每次事件完成期间来发送
case now = 3 // 立刻发送
}
// 通知合并策略
public struct NotificationCoalescing : OptionSet {
public static var none: NotificationQueue.NotificationCoalescing { get } // 不合并
public static var onName: NotificationQueue.NotificationCoalescing { get } // 名字相同时合并
public static var onSender: NotificationQueue.NotificationCoalescing { get } // 发送者(object)相同时合并
}
}
// line:51 全局通知队列链表(单链表)
@interface NotificationQueueList : NSObject
{
@public
NotificationQueueList *next;
NSNotificationQueue *queue;
}
+ (void) registerQueue: (NSNotificationQueue*)q;
+ (void) unregisterQueue: (NSNotificationQueue*)q;
@end
// line:163 通知节点
typedef struct _NSNotificationQueueRegistration
{
// 双向链表前后指针
struct _NSNotificationQueueRegistration *next;
struct _NSNotificationQueueRegistration *prev;
// 通知
NSNotification *notification;
id name;
id object;
NSArray *modes;
} NSNotificationQueueRegistration;
// line:175 通知链表(双向链表)
struct _NSNotificationQueueList;
typedef struct _NSNotificationQueueList
{
struct _NSNotificationQueueRegistration *head;
struct _NSNotificationQueueRegistration *tail;
} NSNotificationQueueList;
// line:226 添加到队列链表
static void add_to_queue(NSNotificationQueueList *queue, NSNotification *notification, NSArray *modes, NSZone *_zone)
{
// 创建队列节点
NSNotificationQueueRegistration *item;
item = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueRegistration));
if (item == 0) {
[NSException raise: NSMallocException format: @"Unable to add to notification queue"];
}
// 设置节点信息
item->notification = RETAIN(notification);
item->name = [notification name];
item->object = [notification object];
item->modes = [modes copyWithZone: [modes zone]];
// 添加进入链表尾部
item->next = NULL;
item->prev = queue->tail;
queue->tail = item;
if (item->prev) {
item->prev->next = item;
}
// 链表为空时,设置该节点为头结点
if (!queue->head) {
queue->head = item;
}
}
@implementation NSNotificationQueue
// line:328 初始化队列
- (id) initWithNotificationCenter: (NSNotificationCenter*)notificationCenter
{
_zone = [self zone];
// init queue 初始化通知队列,持有通知中心,创建 asap、idle 两个通知链表队列
_center = RETAIN(notificationCenter);
_asapQueue = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueList));
_idleQueue = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueList));
if (_asapQueue == 0 || _idleQueue == 0) {
DESTROY(self);
} else {
/*
* insert in global queue list
*/
// 内存分配成功,加入全局的通知队列链表辽中
[NotificationQueueList registerQueue: self];
}
return self;
}
// line:506 入队列
- (void) enqueueNotification: (NSNotification*)notification
postingStyle: (NSPostingStyle)postingStyle
coalesceMask: (NSUInteger)coalesceMask
forModes: (NSArray*)modes
{
if (modes == nil) { // 没有指定 runloop 模式,以默认模式进行发送
modes = defaultMode;
}
if (coalesceMask != NSNotificationNoCoalescing) { // 需要合并的通知,先从队列中移除
[self dequeueNotificationsMatching: notification coalesceMask: coalesceMask];
}
switch (postingStyle) {
case NSPostNow: // 立刻发送
{
NSString *mode;
mode = [[NSRunLoop currentRunLoop] currentMode];
// 当前 runloop 模式,包含在通知需要发送的模式中,直接调用通知中进行发送
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;
}
}
@end
// runloop 进行通知队列中的通知发送
/*
* The following code handles sending of queued notifications by NSRunLoop.
*/
// line:641 asap 队列通知发送
void GSPrivateNotifyASAP(NSString *mode);
// line:660 idle 队列通知发送
void GSPrivateNotifyIdle(NSString *mode);
通知队列创建时需要关联一个通知中心对象,用于进行通知发送;创建 asap、idle 两个通知队列存储加入的通知。 创建的两个通知队列使用双向链表进行通知信息的存储。 根据发送时机不同通知的发送分为三类
- now : 立即发送,只要 runloop 模式匹配,则直接调用通知中心进行通知发送。
- asap : 尽快发送,runloop 会在时间循环间隙,调用该队列进行通知发送。
- idle : 空闲时发送,runloop 闲置期间,调用该队列进行通知发送。