1、通知的移除
(1)针对ViewController
来说:不管是iOS9
之前,还是iOS9
之后,系统在调用dealloc
方法时,会自动调用[[NSNotificationCenter defaultCenter]removeObserver:self]
,所以使用addObserver
添加监听者的话无需手动移除通知。
(2)针对NSObject
来说,iOS9
之前系统在调用dealloc
方法时,不会自动调用[[NSNotificationCenter defaultCenter]removeObserver:self]
,所以需要手动移除,不然会崩溃。iOS9
之后,增加了在调用dealloc
方法时,自动调用[[NSNotificationCenter defaultCenter]removeObserver:self]
的方法,所以添加通知后,无需手动移除通知。
注意:在viewWillAppear
方法里面注册通知,需要在viewWillDisappear
方法里移除通知,不然页面多次调用viewWillAppear
会导致通知多次回调
1. 为什么 iOS9
之前需要手动移除通知了呢?
在iOS9
之前,观察者注册时,通知中心并不会对观察者对象做 retain
操作,而是对观察者对象进行unsafe_unretained
(不安全) 引用。导致观察者对象被释放后,不安全引并不会自动被置为nil,从而变成野指针,对野指针发送消息导致程序崩溃。
2. 什么是unsafe_unretained
引用?
不安全引用(unsafe reference
)和弱引用 (weak reference
) 类似,它并不会让被引用的对象保持存活,但是和弱引用不同的是,当被引用的对象释放的时,不安全引用并不会自动被置为 nil,这就意味着它变成了野指针,而对野指针发送消息会导致程序崩溃。
3. 为什么要进行unsafe_unretained
引用?
因为 Cocoa
和 Cocoa Touch
中的一些类仍然还没有支持 weak
引用。所以,当我们想对这些类使用 weak
引用的时候,只能用unsafe_unretained
来替代。
4. 为什么 iOS9
之后不需要手动移除通知了呢?
从 iOS 9
开始通知中心会对观察者进行 weak
引用,所以不需要在观察者对象释放之前从通知中心移除
但是使用- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
这个方法添加的通知,仍需手动移除通知,因为通知中心对它的持有是强引用
添加通知
_observe = [[NSNotificationCenter defaultCenter]addObserverForName:@"noti3" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%s",__func__);
}];
参数queue:接收到通知的回调在哪个线程中调用,如果传mainQueue则通知在主线程回调,否则在子线程回调
移除通知
[[NSNotificationCenter defaultCenter] removeObserver:_observe];
这个block里面,对self进行了强引用,所以在block使用self时,要进行弱引用,避免循环引用引发的内存泄漏。
2、通知的同步异步
1、使用(void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject
方式添加通知,
(1)默认通知在哪个线程发出,就在哪个线程接收
(2)默认通知的发送和接收和同步的
poster
发出一个通知后,通知事件之后的代码被堵塞,通知中心会一直等待所有的observer
(监听者)都收到,并且处理完通知才会返回到poster
当然为了防止阻塞,可以将通知改成异步
方式1: 将
observer
收到通知后的方法,放在子线程中执行,这种方式接收到通知还是同步,但是接收到通知后的处理
改成异步
- (void)receiveNoti2:(NSNotification *)noti{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"%@",noti.object);
NSLog(@"%@",[NSThread currentThread]);
});
}
方式2、通过
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
和- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes
发送通知,不是让通知回调在异步执行,只是让通知等待到runloop空闲的时候再去执行
发送通知方式如下
NSNotification *noti = [NSNotification notificationWithName:@"noti2" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
NSLog(@"sss=========%@",[NSThread currentThread]);
接收到通知后
- (void)receiveNoti2:(NSNotification *)noti{
NSLog(@"%s",__func__);
NSLog(@"%@",[NSThread currentThread]);
sleep(3);
NSLog(@"nnnnnn ===== %@",[NSThread currentThread]);
}
由图可见,发送通知后,先完成poster
后面的事情,再完成通知的发送和observer
接收到通知后的处理,整个流程都是在主线程中进行,并没有切换线程,只是让通知等待到runloop空闲的时候再去执行
NSPostingStyle
- NSPostWhenIdle:runloop空闲的时候回调通知方法(runloop 进入kCFRunLoopBeforeWaiting)
- NSPostASAP:runloop能够调用的时候就回调通知方法(runloop 进入kCFRunLoopBeforeTimers)
- NSPostNow:runloop立即回调通知方法(和默认通知方式一样)
详情查看iOS 通知的多线程处理 & 与Runloop的关系
coalesceMask
- NSNotificationNoCoalescing = 0, // 不合成
- NSNotificationCoalescingOnName = 1, // 根据NSNotification的name字段进行合成
- NSNotificationCoalescingOnSender = 2 // 根据NSNotification的object字段进行合成
2、使用- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
这个方法添加的通知,通知回调的block在哪个线程执行只和queue参数有关,无论发送通知是在主线程或子线程发送的,都不会影响。