iOS进阶之NSNotification的实现原理
一、NSNotification使用
1、向观察者中心添加观察者:
- 方式一:观察者接收到通知后执行任务的代码在发送通知的线程中执行
- (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
2、通知中心向观察者发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
3、移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
二、实现原理
1、首先了解Observation、NCTable这个结构体内部结构
当你调用addObserver:selector:name:object:会创建一个Observation,Observation的结构如下代码:
typedef struct Obs {
id observer; //接受消息的对象
SEL selector; //执行的方法
struct Obs *next; //下一Obs节点指针
int retained; //引用计数
struct NCTbl *link; //执向chunk table指针
} Observation;
对于Observation持有observer:
在iOS9以前:
- 持有的是一个__unsafe_unretain指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS。
- 在iOS9之后:持有的是weak类型指针,当observer释放时observer会置nil,nil对象performSelector不再会崩溃。
name和Observation是映射关系。
- observer和sel包含在Observation结构体中。
Observation对象存在哪?
NSNotification维护了全局对象表NCTable结构,结构体里包含GSIMapTable表的结构,用于存储Observation。代码如下:
#define CHUNKSIZE 128
#define CACHESIZE 16
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;
数据结构重要的参数:
- wildcard:保存既没有通知名称又没有传入object的通知单链表;
- nameless:存储没有传入名字的通知名称的hash表。
- named:存储传入了名字的通知的hash表。
- cache:用于快速缓存.
这里值得注意nameless和named的结构,虽然都是hash表,存储的东西还有点区别:
- nameless表中的GSIMapTable的结构如下
key | value |
---|---|
object | Observation |
object | Observation |
object | Observation |
没有传入名字的nameless表,key就是object参数,vaule为Observation结构体
- 在named表中GSIMapTable结构如下:
key | value |
---|---|
name | maptable |
name | maptable |
name | maptable |
- maptable也是一个hash表,结构如下:
key | value |
---|---|
object | Observation |
object | Observation |
object | Observation |
传入名字的通知是存放在叫named的hash表
kay为name,value还是maptable也是一个hash表
maptable表的key为object参数,value为Observation参数
2、addObserver:selector:name:object: 方法内部实现原理
- (void) addObserver: (id)observer
selector: (SEL)selector
name: (NSString*)name
object: (id)object
{
Observation *list;
Observation *o;
GSIMapTable m;
GSIMapNode n;
//入参检查异常处理
...
//table加锁保持数据一致性,同一个线程按顺序执行,是同步的
lockNCTable(TABLE);
//创建Observation对象包装相应的调用函数
o = obsNew(TABLE, selector, observer);
//处理存在通知名称的情况
if (name)
{
//table表中获取相应name的节点
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
if (n == 0)
{
//未找到相应的节点,则创建内部GSIMapTable表,以name作为key添加到talbe中
m = mapNew(TABLE);
name = [name copyWithZone: NSDefaultMallocZone()];
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
GS_CONSUMED(name)
}
else
{
//找到则直接获取相应的内部table
m = (GSIMapTable)n->value.ptr;
}
//内部table表中获取相应object对象作为key的节点
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n == 0)
{
//不存在此节点,则直接添加observer对象到table中
o->next = ENDOBS;//单链表observer末尾指向ENDOBS
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
//存在此节点,则获取并将obsever添加到单链表observer中
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
//只有观察者对象情况
else if (object)
{
//获取对应object的table
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n == 0)
{
//未找到对应object key的节点,则直接添加observergnustep-base-1.25.0
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
//找到相应的节点则直接添加到链表中
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
//处理即没有通知名称也没有观察者对象的情况
else
{
//添加到单链表中
o->next = WILDCARD;
WILDCARD = o;
}
//解锁
unlockNCTable(TABLE);
}
添加通知的基本逻辑:
根据传入的selector和observer创建Observation,并存入GSIMaptable中,如果已存在,则是从cache中取。
如果name存在:
- 则向named表中插入元素,key为name,value为GSIMaptable。
- GSIMaptable里面key为object,value为Observation,结束
如果name不存在:
- 则向nameless表中插入元素,key为object,value为Observation,结束
如果name和object都不存在,则把这个Observation添加WILDCARD链表中
三、addObserverForName:object:queueusingBlock:实现原理
//对于block形式,里面创建了GSNotificationObserver对象,然后在调用addObserver: selector: name: object:
- (id) addObserverForName: (NSString *)name
object: (id)object
queue: (NSOperationQueue *)queue
usingBlock: (GSNotificationBlock)block
{
GSNotificationObserver *observer =
[[GSNotificationObserver alloc] initWithQueue: queue block: block];
[self addObserver: observer
selector: @selector(didReceiveNotification:)
name: name
object: object];
return observer;
}
/*
1.初始化该队列会创建Block_copy 拷贝block
2.并确定通知操作队列
*/
- (id) initWithQueue: (NSOperationQueue *)queue
block: (GSNotificationBlock)block
{
self = [super init];
if (self == nil)
return nil;
ASSIGN(_queue, queue);
_block = Block_copy(block);
return self;
}
/*
1.通知的接受处理函数didReceiveNotification,
2.如果queue不为空,通过addOperation来实现指定操作队列处理
3.如果queue不为空,直接在当前线程执行block。
*/
- (void) didReceiveNotification: (NSNotification *)notif
{
if (_queue != nil)
{
GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc]
initWithNotification: notif block: _block];
[_queue addOperation: op];
}
else
{
CALL_BLOCK(_block, notif);
}
}
4、发送通知的实现 postNotificationName: name: object:
- (void) _postAndRelease: (NSNotification*)notification
{
1.入参检查校验
2.创建存储所有匹配通知的数组GSIArray
3.加锁table避免数据一致性问题
4.查找既不监听name也不监听object所有的wildcard类型的Observation,加入数组GSIArray中
5.查找NAMELESS表中指定对应观察者对象object的Observation并添加到数组中
6.查找NAMED表中相应的Observation并添加到数组中
1. 首先查找name与object的一致的Observation加入数组中
2. 当object为nil的Observation加入数组中
3. 当object不为nil,并且object和发送通知的object不一致不为添加到数组中
//解锁table
//遍历整个数组并依次调用performSelector:withObject处理通知消息发送
//解锁table并释放资源
}
二、NSNotification相关问题
1、对于addObserver方法,为什么需要object参数?
- addObserver当你不传入name也可以,传入object,当postNotification方法同样发出这个object时,就会触发通知方法。
因为当name不存在的时候,会继续判断object,则向nameless的maptable表中插入元素,key为object,value为Observation
2、都传入null对象会怎么样
你可能也注意到了,addObserver方法name和object都可以为空,这表示将会把observer赋值为 wildcard,他将会监听所有的通知。
3、通知的发送时同步的,还是异步的。
同步异步这个问题,由于TABLE资源的问题,同一个线程会按顺序遍历数组执行,自然是同步的。
4、NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
由于是使用的performSelector方法,没有进行转线程,默认是postNotification方法的线程。
[o->observer performSelector: o->selector
withObject: notification];
对于异步发送消息,可以使用NSNotificationQueue,queue顾明意思,我们是需要将NSNotification放入queue中执行的。
NSNotificationQueue发送消息的三种模式:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post
NSPostASAP = 2, // 当当前runloop完成之后立即post
NSPostNow = 3 // 立即post
};
NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP];
- NSPostingStyle为NSPostNow 模式是同步发送,
- NSPostWhenIdle或者NSPostASAP是异步发送
5、NSNotificationQueue和runloop的关系?
NSNotificationQueue 是依赖runloop才能成功触发通知,如果去掉runloop的方法,你会发现无法触发通知。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子线程的runloop需要自己主动开启
NSNotification *notification = [NSNotification notificationWithName:@"TEST" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
// run runloop
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
CFRunLoopRun();
NSLog(@"3");
});
NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP];
NSNotificationQueue将通知添加到队列中时,其中postringStyle参数就是定义通知调用和runloop状态之间关系。6、如何保证通知接收的线程在主线程?
保证主线程发送消息或者接受消息方法里切换到主线程
接收到通知后跳转到主线程,苹果建议使用NSMachPort进行消息转发到主线程。
实现代码如下:
7、页面销毁时不移除通知会崩溃吗?
在iOS9之前会,iOS9之后不会
对于Observation持有observer
在iOS9之前:不是一个类似OC中的weak类型,持有的相当与一个__unsafe_unretain指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS。
在iOS9之后:持有的是weak类型指针,对nil对象performSelector不再会崩溃
8、多次添加同一个通知会是什么结果?多次移除通知呢?
由于源码中并不会进行重复过滤,所以添加同一个通知,等于就是添加了2次,回调也会触发两次。
关于多次移除,并没有问题,因为会去map中查找,找到才会删除。当name和object都为nil时,会移除所有关于该observer的WILDCARD
9、下面的方式能接收到通知吗?为什么
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
根据postNotification的实现:
- 会找到key为TestNotification的maptable,
- 再从中选择key为nil的observation,
- 所以是找不到以@1为key的observation的
作者:枫叶无处漂泊
链接:https://www.jianshu.com/p/e93b81fd3aa9