文章目录
问题
苹果并没有开源相关代码,但是可以读下 GNUStep 的源码
或者这里下载
链接:https://pan.baidu.com/s/1F25GgeLxqKjeo10Zgfr2OQ
密码:qpka
从下列问题触发,探索下NSNotification
的实现原理
- 实现原理(结构设计、通知如何存储的、
name&observer&SEL
之间的关系等) - 通知的发送时同步的,还是异步的
NSNotificationCenter
接受消息和发送消息是在一个线程里吗?如何异步发送消息NSNotificationQueue
是异步还是同步发送?在哪个线程响应NSNotificationQueue
和runloop
的关系- 如何保证通知接收的线程在主线程
- 页面销毁时不移除通知会崩溃吗
- 多次添加同一个通知会是什么结果?多次移除通知呢
- 下面的方式能接收到通知吗?为什么
// 发送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
1. 实现原理
对于addObserver:selector:name:object:
会创建一个observation
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;
对于Observation
持有observer
- 在
iOS SDK 8
之前:不是一个类似OC中的weak
类型,持有的相当与一个__unsafe_unretain
指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS
。 - 在
iOS SDK 8
之后:持有的是weak类型指针,对nil对象performSelector
不再会崩溃
name
和Observation
是映射关系,observer
和sel
包含在Observation
结构体中。
此外,NSNotification
维护了GSIMapTable
表的结构,用于存储Observation
,分别是nameless
,name
,cache
,nameless
存储没有传入名字的通知,named
存储传入了名字的通知,cache
用于快速缓存.
#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;
这里值得注意nameless
和named
的结构,虽然同为hash
表
在nameless表中:
GSIMapTable的结构如下
object : Observation
object : Observation
object : Observation
----------------------------
在named表中:
GSIMapTable结构如下:
name : maptable
name : maptable
name : maptable
maptable的结构如下
object : Observation
object : Observation
object : Observation
对于addObserver方法,为什么需要object参数?
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
就是addObserver
其实不用传入name
也可以,传入object
,当postNotification
方法同样发出这个object
时,就会触发通知方法。
例如这样的写法:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
MyObject *obj = [MyObject new];
[center addObserver:self selector:@selector(doAction:) name:nil object:obj];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[center postNotificationName:@"TEST" object:obj];
});
}
- (void)doAction:(NSNotification*)sender {
NSLog(@"%s %@ %@",__FUNCTION__,sender,[NSThread currentThread]);
}
参考 https://www.jianshu.com/p/83770200d476
都传入null对象会怎么样
你可能也注意到了,addObserver
方法name
和object
都可以为空,这表示将会把observer
赋值为 wildcard
,他将会监听所有的通知。
addObserver源码逻辑
addObserver
的逻辑如下
1. 根据传入的selector和observer创建Observation,并存入maptable中,如果已存在,则是从cache中取。
2. 如果name存在,则向named的maptable表中插入元素,key为name,value为GSIMapTable,GSIMapTable中存key为object,value为Observation,转入5。如果不存在则进入3
3. 如果object存在,则向nameless的maptable表中插入元素,key为object,value为Observation。如果不存在,则进入4,否则转入5
4. name和object都为空,则Observation->next=wildcard,将老的wildcard赋值为next指针,然后Observation对象置为wildcard,wildcard = Observation
5. 结束
2.通知的发送时同步的,还是异步的
postNotificationName
的底层实现是
- (void) _postAndRelease: (NSNotification*)notification
由于内部会读取TABLE
lockNCTable(TABLE);
... //找到对应的observer对象
unlockNCTable(TABLE);
...//执行performSelector方法
lockNCTable(TABLE);
GSIArrayEmpty(a); //释放临时创建的数组对象 - 用于存储observer的
unlockNCTable(TABLE);
同步异步这个问题,由于TABLE
资源的问题,同一个线程会按顺序
执行,自然是同步
的。
3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
由于是使用的performSelector
方法,没有进行转线程,默认是postNotification
方法的线程。
[o->observer performSelector: o->selector
withObject: notification];
对于异步发送消息,可以使用NSNotificationQueue
,queue顾明意思,我们是需要将NSNotification
放入queue
中执行的。
有三种状态
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post
NSPostASAP = 2, // 当当前runloop完成之后立即post
NSPostNow = 3 // 立即post
};
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
参考文章 https://www.jianshu.com/p/356f7af4f2ee
4.NSNotificationQueue和runloop的关系
NSNotificationQueue
的执行是依赖于runloop
的,它的三种模式各自的执行时机不一样。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post
NSPostASAP = 2, // 当当前runloop完成之后立即post
NSPostNow = 3 // 立即post
};
例如
void asyncQueueNotiInRunloop() {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
NSLog(@"%@", [NSThread currentThread]);
//NSPostWhenIdle
//NSPostASAP
//NSPostNow
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");
});
}
如果去掉run runloop
部分的代码,则无法触发通知
5.如何保证通知接收的线程在主线程
由于通知的发出使用performSeletor实现,如果需要保证接收的线程在主线程,可以:
- 保证主线程发出
- 接收到通知后跳转到主线程,苹果建议使用
NSMachPort
进行消息转发到主线程。
https://blog.csdn.net/shengpeng3344/article/details/90206265
- 使用
block
接口addObserverForName:object:queue:usingBlock:
- (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.
- 页面销毁时不移除通知会崩溃吗?
对于Observation
持有observer
- 在
iOS SDK 8
之前:不是一个类似OC中的weak
类型,持有的相当与一个__unsafe_unretain
指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS
。 - 在
iOS SDK 8
之后:持有的是weak类型指针,对nil对象performSelector
不再会崩溃
所以说不一定会崩溃,但是根据代码严谨是需要remove
的
6.多次添加同一个通知会是什么结果?多次移除通知呢?
由于源码中并不会进行重复过滤,所以添加同一个通知,等于就是添加了2次,回调也会触发两次。
为什么会触发两次呢,因为- (void) postNotificationName: (NSString*)name object: (id)object
的逻辑是这样的:
1. 查找所有是wildcard类型的Observation,加入数组array,即既不监听name也不监听object
2. 查找指定object但未指定name的Observation,加入数组array
3. 查找指定了相同name和object的Observation,加入数组array,当name一致但object不一致时,也不会加入到数组array,但如果传入的object!=nil,则会将name对应的maptable中,所有key为nil的Observation也加入数组。
4. 遍历array,执行performSelector
5. 清空array
第三步的意思是:如果发出一个通知,方法中传入了对象object
,那么那些只监听通知name
,object
设置为nil
的当然也可以收到,object
匹配了的也可以收到,object
不匹配的则收不到。
关于多次移除,并没有问题,因为会去map
中查找,找到才会删除。当name
和object
都为nil
时,会移除所有关于该observer
的WILDCARD
7.下面的方式能接收到通知吗?为什么
[[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
的
内容太多,有错误还望指出下,我也才每隔一段时间重新审视下博文
是不是对一个人越热情,那个人对你就越冷