Understand the interview questions from the bottom - NSNotification

1. Model structure

1.NSNotification

@interface NSNotification : NSObject <NSCopying, NSCoding>
// 通知的名称
@property (readonly, copy) NSNotificationName name;
// 通知携带的对象
@property (nullable, readonly, retain) id object;
// 发送通知时所存入的扩展信息
@property (nullable, readonly, copy) NSDictionary *userInfo;

// 初始化一个通知对象
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
@end

@interface NSNotification (NSNotificationCreation)
// 创建一个通知
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
// 创建一个通知
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
- (instancetype)init /*API_UNAVAILABLE(macos, ios, watchos, tvos)*/;	/* do not invoke; not a valid initializer for this class */

@end
复制代码

This object is returned when a notification is received

2.NSNotificationCenter

@interface NSNotificationCenter : NSObject {
    @package
    void *_impl;
    void *_callback;
    void *_pad[11];
}

// 默认的通知中心,全局唯一
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
// 在通知中心注册一个观察者
- (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;
// 在通知中心注册一个观察者,block回调的形式代替selector
- (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));
    // The return value is retained by the system, and should be held onto by the caller in
    // order to remove the observer with removeObserver: later, to stop observation.

@end
复制代码

It is a singleton class, responsible for managing the creation and sending of notifications, mainly doing three things

  • Registration Notice
  • send notification
  • Takedown notice

There are currently two ways to register a notification: selectorandblock

//注册观察者
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (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));
复制代码

For the method of adding the specified observer object, it observercannot be nil; blockthe method will execute the copymethod, return the anonymous observer object used, and specify the operation object of the observer to process the message NSOperationQueue;

For the specified message name nameand sender object object, it can be empty, that is, to receive all messages and messages sent by all sender objects; if one or both of them are specified, it means to receive messages from the specified message name and sender;

For the queue blockspecified by the mode , it can be processed by the message sending thread by default; if the main queue is specified, that is, the main thread is processed, so as to avoid exceptions caused by the execution of operations;queuenilUI

Note: Registering observer notification messages should avoid repeated registration, which will result in repeated processing of notification messages and blockhold external objects, so it is necessary to avoid causing circular reference problems;

send notification

//发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
复制代码

The message can be sent through NSNotificationthe wrapped notification message object, or the message name, sender and carried information can be specified separately to send, and it is in synchronous execution mode. It needs to wait for all registered observers to process the notification message before the method returns to continue. Execute down, and for the blockformal processing notification object, it is executed in the queue specified by the registered message, and for the non- modal processing, blockit is processed in the same thread.

Note: The message sending type needs to be the same as the registration type, that is, if the registered observer specifies the message name and sender at the same time, the sending message also needs to be specified at the same time, otherwise the message cannot be received.

Takedown notice

//移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
复制代码

可移除指定的观察者所有通知消息,即该观察者不再接收任何消息,一般用于观察者对象dealloc释放后调用,但在ios9macos10.11之后不需要手动调用,dealloc已经自动处理;

3.NSNotificationQueue

@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
复制代码

可以通过defaultQueue获取当前线程绑定的通知消息队列,也可以通过initWithNotificationCenter:来指定通知管理中心

该对象主要做两件事

  1. 添加通知到队列
  2. 删除通知

通知发送时机

// 通知的发送时机
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // runloop空闲时发送通知
    NSPostASAP = 2,     // 在当前通知调用或者计时器结束发出通知
    NSPostNow = 3       // 立刻发送或在合并通知完成之后发送
};
复制代码

通知合并策略

// 通知合并的策略,有些时候同名通知只想存在一个,这时候就可以用到它了
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,// 默认不合并
    NSNotificationCoalescingOnName = 1,// 按照通知名字合并通知
    NSNotificationCoalescingOnSender = 2// 按照传入的object合并通知
};
复制代码

异步通知的使用

NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
复制代码

通知合并的使用

// 发送多个通知
NSNotification *noti = [NSNotification notificationWithName:@"NSNotification" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil];

// 注册名为NSNotification的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:*@selector(noti:) name:@"NSNotification" object:nil];

- (void) noti:(NSNotification *) noti {
    NSLog(@"noti");
}
复制代码

coalesceMask 设置为 NSNotificationNoCoalescing 结果打印了三个noti 当 coalesceMask 设置为 NSNotificationCoalescingOnName 结果打印了一个noti

4.GSNotificationObserver

@implementation GSNotificationObserver {
    NSOperationQueue *_queue; // 保存传入的队列
    GSNotificationBlock _block; // 保存传入的block
}
- (id) initWithQueue: (NSOperationQueue *)queue 
               block: (GSNotificationBlock)block {
    ......初始化操作
}

- (void) dealloc {
    ....
}

// 响应接收通知的方法,并在指定队列中执行block
- (void) didReceiveNotification: (NSNotification *)notif {
    if (_queue != nil){
            GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] 
                    initWithNotification: notif block: _block];
            [_queue addOperation: op];
    } else {
        CALL_BLOCK(_block, notif);
    }
}

@end

复制代码

这个类是GNUStep源码中定义的,它的作用是代理观察者,主要用来实现接口:addObserverForName:object: queue: usingBlock:时用到,即要实现在指定队列回调block,那么GSNotificationObserver对象保存了queueblock信息,并且作为观察者注册到通知中心,等到接收通知时触发了响应方法,并在响应方法中把block抛到指定queue中执行

二.底层分析

1._GSIMapTable

通知的底层离不开这张映射表

*  A rough picture is include below:
 *   
 *  
 *   This is the map                C - array of the buckets
 *   +---------------+             +---------------+
 *   | _GSIMapTable  |      /----->| nodeCount     |  
 *   |---------------|     /       | firstNode ----+--\  
 *   | buckets    ---+----/        | ..........    |  |
 *   | bucketCount  =| size of --> | nodeCount     |  |
 *   | nodeChunks ---+--\          | firstNode     |  |
 *   | chunkCount  =-+\ |          |     .         |  | 
 *   |   ....        || |          |     .         |  |
 *   +---------------+| |          | nodeCount     |  |
 *                    | |          | fistNode      |  | 
 *                    / |          +---------------+  | 
 *         ----------   v                             v
 *       /       +----------+      +---------------------------+ 
 *       |       |  * ------+----->| Node1 | Node2 | Node3 ... | a chunk
 *   chunkCount  |  * ------+--\   +---------------------------+
 *  is size of = |  .       |   \  +-------------------------------+
 *               |  .       |    ->| Node n | Node n + 1 | ...     | another
 *               +----------+      +-------------------------------+
 *               array pointing
 *               to the chunks

复制代码

结构定义

#if	!defined(GSI_MAP_TABLE_T)
typedef struct _GSIMapBucket GSIMapBucket_t;
typedef struct _GSIMapNode GSIMapNode_t;

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.	*/
};

#if	defined(GSI_MAP_TABLE_T)
typedef GSI_MAP_TABLE_T	*GSIMapTable;
#else
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
};

复制代码

解析 _GSIMapTable 数据结构

1.从bucket的角度,看成一个单向链表

  • buckets是一个单向链表,存储着GSIMapBucketGSIMapBucket中其firstNode->nextInBucket表示下一个bucketfirstNode表示另一条单链表的首个元素。
  • bucketCount表示buckets的数量

2.从chunk角度,看成一个数组指针

  • nodeChunks 表示一个数组指针,数组存储所有单链表的首个元素node
  • chunkCount 表示数组大小

此外freeNodes则就是需要释放的元素,是一个单向链表。
其实就是一个hash表结构,既可以以数组的形式取到每个单向链表首元素,也可以以链表形式取得。
通过数组能够方便取到每个单向链表,再利用链表结构方便增删。

//取单向链表
//通过hash值%最大个数 来获取index,然后取出单向链表,再进行链表增删
GS_STATIC_INLINE GSIMapBucket
GSIMapPickBucket(unsigned hash, GSIMapBucket buckets, uintptr_t bucketCount) {
  return buckets + hash % bucketCount;
}

GS_STATIC_INLINE GSIMapBucket
GSIMapBucketForKey(GSIMapTable map, GSIMapKey key) {
  return GSIMapPickBucket(GSI_MAP_HASH(map, key),
    map->buckets, map->bucketCount);
}

//增删元素
GS_STATIC_INLINE void
GSIMapLinkNodeIntoBucket(GSIMapBucket bucket, GSIMapNode node) {
  node->nextInBucket = bucket->firstNode;
  bucket->firstNode = node;
}

GS_STATIC_INLINE void
GSIMapUnlinkNodeFromBucket(GSIMapBucket bucket, GSIMapNode node) {
  if (node == bucket->firstNode) {
      bucket->firstNode = node->nextInBucket;
  } else {
      GSIMapNode tmp = bucket->firstNode;
      while (tmp->nextInBucket != node) {
	  tmp = tmp->nextInBucket;
	}
      tmp->nextInBucket = node->nextInBucket;
    }
  node->nextInBucket = 0;
}
复制代码

这是OC中实现hash表的一种形式。但对于Javahashmap来说,更为简单了。hashmap涉及红黑树等查找问题,后续贴出对比下。

2.存储容器NCTbl

通知全局对象表结构

// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
    Observation		*wildcard;	/* 链表结构,保存既没有name也没有object的通知*/
    GSIMapTable		nameless;	/* 存储没有name但是有object的通知*/
    GSIMapTable		named;		/* 存储带有name的通知,不管有没有object*/
    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;

// Observation 存储观察者和响应结构体,基本的存储单元
typedef struct Obs { 
    id observer;      /* 观察者,接收通知的对象 */ 
    SEL selector;     /* 响应方法 */ 
    struct Obs *next; /* Next item in linked list. */ 
    ... 
} Observation;

复制代码

保存含有通知名称的通知表named需要注册object对象,因此该表结构体通过传入的name作为key,其中value同时也为GSIMapTable表用于存储对应的object对象的observer对象;

对没有传入通知名称只传入object对象的通知表nameless而言,只需要保存objectobserver的对应关系,因此object作为keyobserver作为value

3.注册通知

核心源码- Selector 形式

/*
observer:观察者,即通知的接收者
selector:接收到通知时的响应方法
name: 通知name
object:携带对象
*/
- (void) addObserver: (id)observer
	    	selector: (SEL)selector
                name: (NSString*)name 
                object: (id)object {
  // 前置条件判断
  ......

  // 创建一个observation对象,持有观察者和SEL,下面进行的所有逻辑就是为了存储它
  o = obsNew(TABLE, selector, observer);
  
/*======= case1: 如果name存在 =======*/
  if (name) {
 	//-------- NAMED是个宏,表示名为named字典。以name为key,从named表中获取对应的mapTable
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      if (n == 0) { // 不存在,则创建 
          m = mapNew(TABLE); // 先取缓存,如果缓存没有则新建一个map
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          ...
	  }
      else { // 存在则把值取出来 赋值给m
          m = (GSIMapTable)n->value.ptr;
	  }
 	//-------- 以object为key,从字典m中取出对应的value,其实value被MapNode的结构包装了一层,这里不追究细节
      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;
      }
    }
/*======= case2:如果name为空,但object不为空 =======*/
  else if (object) {
  	// 以object为key,从nameless字典中取出对应的value,value是个链表结构
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      // 不存在则新建链表,并存到map中
      if (n == 0) { 
          o->next = ENDOBS;
          GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
	  }
      else { // 存在 则把值接到链表的节点上
		...
	  }
    }
/*======= case3:name 和 object 都为空 则存储到wildcard链表中 =======*/
  else {
      o->next = WILDCARD;
      WILDCARD = o;
  }
}

复制代码

NCTable结构体中核心的三个变量以及功能:wildcardnamednameless,在源码中直接用宏定义表示了:WILDCARDNAMELESSNAMED

case1: 存在name(无论object是否存在)

  1. 注册通知,如果通知的name存在,则以name为key从named字典中取出值n(这个n其实被MapNode包装了一层,便于理解这里直接认为没有包装),这个n还是个字典,各种判空新建逻辑不讨论
  2. 然后以object为key,从字典n中取出对应的值,这个值就是Observation类型(后面简称obs)的链表,然后把刚开始创建的obs对象o存储进去

数据结构关系图: image.png

如果注册通知时传入name,那么会是一个双层的存储结构

  1. 找到NCTable中的named表,这个表存储了还有name的通知
  2. name作为key,找到value,这个value依然是一个map
  3. map的结构是以object作为key,obs对象为value,这个obs对象的结构上面已经解释,主要存储了observer & SEL

case2: 只存在object

  1. object为key,从nameless字典中取出value,此value是个obs类型的链表
  2. 把创建的obs类型的对象o存储到链表中

数据结构关系图:
image.png
只存在object时存储只有一层,那就是objectobs对象之间的映射

case3: 没有name和object

这种情况直接把obs对象存放在了Observation  *wildcard  链表结构中

核心源码- block 形式

block形式只是 Selector 函数的包装

- (id) addObserverForName: (NSString *)name 
                   object: (id)object 
                    queue: (NSOperationQueue *)queue 
               usingBlock: (GSNotificationBlock)block {
    // 创建一个临时观察者
    GSNotificationObserver *observer = 
        [[GSNotificationObserver alloc] initWithQueue: queue block: block];
    // 调用了selector的注册方法
    [self addObserver: observer 
             selector: @selector(didReceiveNotification:) 
                 name: name 
               object: object];
    return observer;
}

- (id) initWithQueue: (NSOperationQueue *)queue 
               block: (GSNotificationBlock)block {
    self = [super init];
    if (self == nil)
        return nil;
    ASSIGN(_queue, queue);
    _block = Block_copy(block);
    return self;
}

- (void) didReceiveNotification: (NSNotification *)notif {
    if (_queue != nil) {
        GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] 
            initWithNotification: notif block: _block];
        [_queue addOperation: op];
    } else {
        CALL_BLOCK(_block, notif);
    }
}

复制代码

这个接口依赖于selector,只是多了一层代理观察者GSNotificationObserver

  1. 创建一个GSNotificationObserver类型的对象observer,并把queueblock保存下来
  2. 调用接口1进行通知的注册
  3. 接收到通知时会响应observerdidReceiveNotification:方法,然后在didReceiveNotification:中把block抛给指定的queue去执行

4.发送通知

核心代码

// 发送通知
- (void) postNotificationName: (NSString*)name
		       object: (id)object
		     userInfo: (NSDictionary*)info {
// 构造一个GSNotification对象, GSNotification继承了NSNotification
  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];
}

//发送通知的核心函数,主要做了三件事:查找通知、发送、释放资源
- (void) _postAndRelease: (NSNotification*)notification {
    //step1: 从named、nameless、wildcard表中查找对应的通知
    ...
    //step2:执行发送,即调用performSelector执行响应方法,从这里可以看出是同步的
   	[o->observer performSelector: o->selector
                    withObject: notification];
	//step3: 释放资源
    RELEASE(notification);
}
复制代码

上述代码主要做了三件事

  1. 通过name & object 查找到所有的obs对象(保存了observersel),放到数组中
  2. 通过performSelector:逐一调用sel,这是个同步操作
  3. 释放notification对象

源码逻辑可以看出发送过程的概述

  1. 从三个存储容器中:namednamelesswildcard去查找对应的obs对象
  2. 然后通过performSelector:逐一调用响应方法,这就完成了发送流程

5.删除通知

  1. 查找时仍然以nameobject为维度的,再加上observer做区分
  2. 因为查找时做了这个链表的遍历,所以删除时会把重复的通知全都删除掉
// 删除已经注册的通知
- (void) removeObserver: (id)observer
		   name: (NSString*)name
                 object: (id)object {
  if (name == nil && object == nil && observer == nil)
      return;
      ...
}

- (void) removeObserver: (id)observer{
  if (observer == nil)
      return;
  [self removeObserver: observer name: nil object: nil];
}
复制代码

6.异步通知

NSNotificationQueue的异步发送,从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop的时机来触发的

逻辑

  1. 根据coalesceMask参数判断是否合并通知
  2. 接着根据postingStyle参数,判断通知发送的时机,如果不是立即发送则把通知加入到队列中:_asapQueue_idleQueue

核心点

  1. 队列是双向链表实现
  2. 当postingStyle值是立即发送时,调用的是NSNotificationCenter进行发送的,所以NSNotificationQueue还是依赖NSNotificationCenter进行发送
/*
* 把要发送的通知添加到队列,等待发送
* NSPostingStyle 和 coalesceMask在上面的类结构中有介绍
* modes这个就和runloop有关了,指的是runloop的mode
*/ 
- (void) enqueueNotification: (NSNotification*)notification
		postingStyle: (NSPostingStyle)postingStyle
		coalesceMask: (NSUInteger)coalesceMask
		    forModes: (NSArray*)modes {
	......
  // 判断是否需要合并通知
  if (coalesceMask != NSNotificationNoCoalescing) {
      [self dequeueNotificationsMatching: notification
			    coalesceMask: coalesceMask];
  }
  switch (postingStyle) {
      case NSPostNow: {
      	...
      	// 如果是立马发送,则调用NSNotificationCenter进行发送
	     [_center postNotification: notification];
         break;
	  }
      case NSPostASAP:
      	// 添加到_asapQueue队列,等待发送
		add_to_queue(_asapQueue, notification, modes, _zone);
		break;

      case NSPostWhenIdle:
        // 添加到_idleQueue队列,等待发送
		add_to_queue(_idleQueue, notification, modes, _zone);
		break;
    }
}
复制代码

异步发送通知

  • runloop触发某个时机,调用GSPrivateNotifyASAP()GSPrivateNotifyIdle()方法,这两个方法最终都调用了notify()方法

  • notify()所做的事情就是调用NSNotificationCenterpostNotification:进行发送通知

static void notify(NSNotificationCenter *center, 
                   NSNotificationQueueList *list,
                   NSString *mode, NSZone *zone) {
 	......
    // 循环遍历发送通知
    for (pos = 0; pos < len; pos++) {
	  NSNotification *n = (NSNotification*)ptr[pos];
	  [center postNotification: n];
	  RELEASE(n);
	}
	......	
}

// 发送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode) {
    notify(item->queue->_center,
        item->queue->_asapQueue,
        mode,
        item->queue->_zone);
}

// 发送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode) {
    notify(item->queue->_center,
    	item->queue->_idleQueue,
    	mode,
    	item->queue->_zone);
}
复制代码

对于NSNotificationQueue总结如下

  1. 依赖runloop,所以如果在其他子线程使用NSNotificationQueue,需要开启runloop
  2. 最终还是通过NSNotificationCenter进行发送通知,所以这个角度讲它还是同步的
  3. 所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程

主线程响应异步通知

异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出问题,那么如何保证在主线程响应通知呢?

其实也是比较常见的问题了,基本上解决方式如下几种:

  1. 使用addObserverForName: object: queue: usingBlock方法注册通知,指定在mainqueue上响应block
 // 代码
@interface A : NSObject <NSMachPortDelegate>
- (void)test;
@end
@implementation A
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
    [[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
        // 刷新UI ...
    }];
}
@end
 
// 输出
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017-02-27 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} 刷新UI
复制代码
  1. 在主线程注册一个machPort,它是用来做线程通信的,当在异步线程收到通知,然后给machPort发送消息,这样肯定是在主线程处理的,具体用法
// 代码
#import <CoreFoundation/CFRunLoop.h>
@interface A : NSObject <NSMachPortDelegate>
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;
- (void)setUpThreadingSupport;
- (void)handleMachMessage:(void *)msg;
- (void)processNotification:(NSNotification *)notification;
- (void)test;
@end
@implementation A
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
    [self setUpThreadingSupport];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"111" object:nil];
}
- (void)setUpThreadingSupport {
    if (self.notifications) {
        return;
    }
    self.notifications      = [[NSMutableArray alloc] init];
    self.notificationLock   = [[NSLock alloc] init];
    self.notificationThread = [NSThread mainThread];
    self.notificationPort = [[NSMachPort alloc] init];
    [self.notificationPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                forMode:(NSString *)kCFRunLoopCommonModes];
}
- (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] != self.notificationThread) {
        [self.notificationLock lock];
        [self.notifications addObject:notification];
        [self.notificationLock unlock];
        [self.notificationPort sendBeforeDate:[NSDate date]
                                   components:nil
                                         from:nil
                                     reserved:0];
    } else {
        NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
        // 刷新UI ...
    }
}
@end

// 输出
2017-02-27 11:49:04.296 notification[29036:12827315] current thread <NSThread: 0x7be3e000>{number = 3, name = (null)}
2017-02-27 11:49:04.307 notification[29036:12827268] current thread <NSThread: 0x7bf3b970>{number = 1, name = main} 刷新UI
复制代码

三.问题

1.observer 的对象存储在何处?

// NSNotificationCenter的init方法
- (id) init {
  if ((self = [super init]) != nil) {
      _table = newNCTable();
    }
  return self;
}
复制代码

每个 NSNotificationCenter 都有一个默认的 _table。其对 observer 进行引用(iOS9以前 unsafe_unretained,iOS9以后 weak)。

2.post 的时候根据何种方式查找接受通知的对象?

仔细看一看【核心源码- Selector 形式
table 中查找 observer object 的时候,首先根据的是 object,接下来根据的是 name,可见 name 的优先级比较

3.系统的通知 Name 有哪些?

// 当程序被推送到后台时
UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification       NS_AVAILABLE_IOS(4_0);
// 当程序从后台将要重新回到前台时
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification      NS_AVAILABLE_IOS(4_0);
// 当程序完成载入后通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
// 应用程序转为激活状态时
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
// 用户按下主屏幕按钮调用通知,并未进入后台状态
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
// 内存较低时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
// 当程序将要退出时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
// 当系统时间发生改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;
// 当StatusBar框方向将要变化时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation
// 当StatusBar框方向改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED;  // userInfo contains NSNumber with old orientation
// 当StatusBar框Frame将要改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED;       // userInfo contains NSValue with new frame
// 当StatusBar框Frame改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED;        // userInfo contains NSValue with old frame
// 后台下载状态发生改变时通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
// 受保护的文件当前变为不可用时通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable    NS_AVAILABLE_IOS(4_0);
// 受保护的文件当前变为可用时通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable       NS_AVAILABLE_IOS(4_0);
// 截屏通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);
复制代码

四.面试题

1.通知的实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)

通知中心结构大概分为如下几个类

  • NSNotification 通知的模型 name、object、userinfo.
  • NSNotificationCenter通知中心 负责发送NSNotification
  • NSNotificationQueue通知队列 负责在某些时机触发 调用NSNotificationCenter通知中心 post通知

通知是结构体通过双向链表进行数据存储

// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
  Observation       *wildcard;  /* 链表结构,保存既没有name也没有object的通知 */
  GSIMapTable       nameless;   /* 存储没有name但是有object的通知 */
  GSIMapTable       named;      /* 存储带有name的通知,不管有没有object  */
    ...
} NCTable;

// Observation 存储观察者和响应结构体,基本的存储单元
typedef struct  Obs {
  id        observer;   /* 观察者,接收通知的对象  */
  SEL       selector;   /* 响应方法     */
  struct Obs    *next;      /* Next item in linked list.    */
  ...
} Observation;
复制代码

主要是以key value的形式存储,这里需要重点强调一下 通知以 nameobject两个纬度来存储相关通知内容,也就是我们添加通知的时候传入的两个不同的方法.

name&observer&SEL之间的关系
name作为keyobserver作为观察者对象,当合适时机触发就会调用observerSEL

2.通知的发送时同步的,还是异步的

同步发送,因为要调用消息转发.
所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程.

3.NSNotificationCenter 接受消息和发送消息是在一个线程里吗?如何异步发送消息

是的, 异步线程发送通知则响应函数也是在异步线程.
异步发送通知可以开启异步线程发送即可.

4.NSNotificationQueue是异步还是同步发送?在哪个线程响应?

// 表示通知的发送时机
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // runloop空闲时发送通知
    NSPostASAP = 2, // 尽快发送,这种时机是穿插在每次事件完成期间来做的
    NSPostNow = 3 // 立刻发送或者合并通知完成之后发送
};
复制代码
. NSPostWhenIdle NSPostASAP NSPostNow
NSPostingStyle 异步发送 异步发送 同步发送

NSNotificationCenter都是同步发送的,而这里介绍关于NSNotificationQueue的异步发送,从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop的时机来触发的.

异步线程发送通知则响应函数也是在异步线程,主线程发送则在主线程.

5.NSNotificationQueue和runloop的关系

NSNotificationQueue依赖runloop. 因为通知队列要在runloop回调的某个时机调用通知中心发送通知.从下面的枚举值就能看出来

// 表示通知的发送时机
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // runloop空闲时发送通知
    NSPostASAP = 2, // 尽快发送,这种时机是穿插在每次事件完成期间来做的
    NSPostNow = 3 // 立刻发送或者合并通知完成之后发送
};
复制代码

6.如何保证通知接收的线程在主线程

如果想在主线程响应异步通知的话可以用如下两种方式

  1. 系统接受通知的API指定队列
  2. NSMachPort的方式 通过在主线程的 runloop 中添加 machPort,设置这个 portdelegate,通过这个 Port 其他线程可以跟主线程通信,在这个 port 的代理回调中执行的代码肯定在主线程中运行,所以,在这里调用 NSNotificationCenter 发送通知即可

7.页面销毁时不移除通知会崩溃吗?

iOS9.0之前,会crash,原因:通知中心对观察者的引用是 unsafe_unretained,导致当观察者释放的时候,观察者的指针值并不为nil,出现野指针.

iOS9.0之后,不会crash,原因:通知中心对观察者的引用是 weak

8.多次添加同一个通知会是什么结果?多次移除通知呢

多次添加同一个通知,会导致发送一次这个通知的时候,响应多次通知回调。多次移除通知不会产生crash。

9.下面的方式能接收到通知吗?为什么

// 发送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
复制代码

不能

通知中心存储通知观察者的结构

// 根容器,NSNotificationCenter持有
typedef struct NCTbl {
  Observation  *wildcard;    /* 链表结构,保存既没有name也没有object的通知 */
  GSIMapTable nameless;    /* 存储没有name但是有object的通知    */
  GSIMapTable named;        /* 存储带有name的通知,不管有没有object    */
    ...
} NCTable;
复制代码

当添加通知监听的时候,我们传入了nameobject,所以,观察者的存储链表是这样的:
named表:key(name) : value->key(object) : value(Observation)

因此在发送通知的时候,如果只传入name而并没有传入object,是找不到Observation的,也就不能执行观察者回调.

Guess you like

Origin juejin.im/post/7078958149331320839