文章目录
问题
1. 实现原理
对于addObserver方法,为什么需要object参数?
都传入null对象会怎么样
addObserver源码逻辑
2.通知的发送时同步的,还是异步的
3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
4.NSNotificationQueue和runloop的关系
5.如何保证通知接收的线程在主线程
6.多次添加同一个通知会是什么结果?多次移除通知呢?
7.下面的方式能接收到通知吗?为什么
问题
苹果并没有开源相关代码,但是可以读下 GNUStep 的源码
或者这里下载
链接:https://pan.baidu.com/s/1F25GgeLxqKjeo10Zgfr2OQ
密码:qpka
从下列问题触发,探索下NSNotification的实现原理
实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)
通知的发送时同步的,还是异步的
NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
NSNotificationQueue是异步还是同步发送?在哪个线程响应
NSNotificationQueue和runloop的关系
如何保证通知接收的线程在主线程
页面销毁时不移除通知会崩溃吗
多次添加同一个通知会是什么结果?多次移除通知呢
下面的方式能接收到通知吗?为什么
1 |
// 发送通知 |
2 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@”TestNotification” object:@1]; |
3 |
// 接收通知 |
4 |
[NSNotificationCenter.defaultCenter postNotificationName:@”TestNotification” object:nil]; |
1. 实现原理
对于addObserver:selector:name:object:会创建一个observation
1 |
typedef |
2 |
id |
3 |
SEL |
4 |
struct Obs |
5 |
int |
6 |
struct NCTbl |
7 |
} 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用于快速缓存.
1 |
#define |
CHUNKSIZE |
128 |
|
|
|
2 |
#define |
CACHESIZE |
16 |
|
|
|
3 |
typedef struct NCTbl { |
|
|
|
|
|
4 |
Observation |
|
*wildcard; |
/* Get ALL messages. |
|
*/ |
5 |
GSIMapTable |
|
nameless; |
/* Get messages for any name. |
*/ |
|
6 |
GSIMapTable |
|
named; |
|
/* Getting named messages only. |
*/ |
7 |
unsigned |
|
lockCount; |
/* Count recursive operations. |
*/ |
|
8 |
NSRecursiveLock |
*_lock; |
|
/* Lock out other threads. |
*/ |
|
9 |
Observation |
|
*freeList; |
|
|
|
10 |
Observation |
|
**chunks; |
|
|
|
11 |
unsigned |
|
numChunks; |
|
|
|
12 |
GSIMapTable |
|
cache[CACHESIZE]; |
|
|
|
13 |
unsigned short |
chunkIndex; |
|
|
|
|
14 |
unsigned short |
cacheIndex; |
|
|
|
|
15 |
} NCTable; |
|
|
|
|
|
这里值得注意nameless和named的结构,虽然同为hash表
1 |
|
2 |
在nameless表中: |
3 |
GSIMapTable的结构如下 |
4 |
object : Observation |
5 |
object : Observation |
6 |
object : Observation |
7 |
|
8 |
—————————- |
9 |
在named表中: |
10 |
GSIMapTable结构如下: |
11 |
name : maptable |
12 |
name : maptable |
13 |
name : maptable |
14 |
|
15 |
maptable的结构如下 |
16 |
object : Observation |
17 |
object : Observation |
18 |
object : Observation |
关于GSIMap结构
对于addObserver方法,为什么需要object参数?
– (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
就是addObserver其实不用传入name也可以,传入object,当postNotification方法同样发出这个object时,就会触发通知方法。
例如这样的写法:
1 |
– (void)viewDidLoad { |
2 |
[super viewDidLoad]; |
3 |
// Do any additional setup after loading the view. |
4 |
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; |
5 |
MyObject *obj = [MyObject new]; |
6 |
[center addObserver:self selector:@selector(doAction:) name:nil object:obj]; |
7 |
dispatch_async(dispatch_get_global_queue(0, 0), ^{ |
8 |
[center postNotificationName:@”TEST” object:obj]; |
9 |
}); |
10 |
} |
11 |
|
12 |
– (void)doAction:(NSNotification*)sender { |
13 |
NSLog(@”%s %@ %@”,__FUNCTION__,sender,[NSThread currentThread]); |
14 |
} |
参考 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方法
1 |
lockNCTable(TABLE); |
2 |
… //找到对应的observer对象 |
3 |
unlockNCTable(TABLE); |
4 |
|
5 |
…//执行performSelector方法 |
6 |
|
7 |
lockNCTable(TABLE); |
8 |
GSIArrayEmpty(a); //释放临时创建的数组对象 – 用于存储observer的 |
9 |
unlockNCTable(TABLE); |
同步异步这个问题,由于TABLE资源的问题,同一个线程会按顺序执行,自然是同步的。
3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
由于是使用的performSelector方法,没有进行转线程,默认是postNotification方法的线程。
1 |
|
2 |
[o->observer performSelector: o->selector |
3 |
withObject: notification]; |
4 |
|
对于异步发送消息,可以使用NSNotificationQueue,queue顾明意思,我们是需要将NSNotification放入queue中执行的。
有三种状态
1 |
typedef NS_ENUM(NSUInteger, NSPostingStyle) { |
2 |
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post |
3 |
NSPostASAP = 2, // 当当前runloop完成之后立即post |
4 |
NSPostNow = 3 // 立即post |
5 |
}; |
1 |
NSNotification *noti = [NSNotification notificationWithName:@”111″ object:nil]; |
2 |
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP]; |
参考文章 https://www.jianshu.com/p/356f7af4f2ee
4.NSNotificationQueue和runloop的关系
NSNotificationQueue的执行是依赖于runloop的,它的三种模式各自的执行时机不一样。
1 |
typedef NS_ENUM(NSUInteger, NSPostingStyle) { |
2 |
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post |
3 |
NSPostASAP = 2, // 当当前runloop完成之后立即post |
4 |
NSPostNow = 3 // 立即post |
5 |
}; |
例如
1 |
void asyncQueueNotiInRunloop() { |
2 |
dispatch_async(dispatch_get_global_queue(0, 0), ^{ |
3 |
NSLog(@”1″); |
4 |
NSLog(@”%@”, [NSThread currentThread]); |
5 |
|
6 |
//NSPostWhenIdle |
7 |
//NSPostASAP |
8 |
//NSPostNow |
9 |
NSNotification *notification = [NSNotification notificationWithName:@”TEST” object:nil]; |
10 |
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]]; |
11 |
// run runloop |
12 |
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes]; |
13 |
CFRunLoopRun(); |
14 |
NSLog(@”3″); |
15 |
}); |
16 |
} |
如果去掉run runloop部分的代码,则无法触发通知
5.如何保证通知接收的线程在主线程
由于通知的发出使用performSeletor实现,如果需要保证接收的线程在主线程,可以:
保证主线程发出
接收到通知后跳转到主线程,苹果建议使用NSMachPort进行消息转发到主线程。
https://blog.csdn.net/shengpeng3344/article/details/90206265
使用block接口addObserverForName:object:queue:usingBlock:
1 |
– (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)); |
2 |
// The return value is retained by the system, and should be held onto by the caller in |
3 |
// 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.下面的方式能接收到通知吗?为什么
1 |
|
2 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@”TestNotification” object:@1]; |
3 |
|
4 |
[NSNotificationCenter.defaultCenter postNotificationName:@”TestNotification” object:nil]; |
根据postNotification的实现,会找到key为TestNotification的maptable,再从中选择key为nil的observation,所以是找不到以@1为key的observation的