1. 问题导入
由于平时写代码的习惯,每次在viewWillAppear:或init中添加通知后,都会在viewWillDisappear:或者dealloc里调用 removeObserver:name:object: 逐一移除通知。
如果没有移除通知,是否会崩溃,这问题在之前还真没有考虑过。
实践出真知,写个 Demo 测试一下。
2. Demo测试
NSNotificationCenter 添加通知的方式,有两种。
- (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;
下面对这两种添加逐一进行测试。
第一步,给 ViewController 添加跳转至 TestViewController 的按钮,和发送通知的按钮。
第二步,在 TestViewController 显示时添加通知,不需要在任何时机移除。
第三步,从 ViewController 进入 TestViewController,然后返回 ViewController,点击发送通知,确认是否崩溃。
// ViewController.m
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)toTestButtonEvent:(id)sender {
NSLog(@"%s", __FUNCTION__);
// 跳转到TestViewController
[self performSegueWithIdentifier:@"toTest" sender:nil];
}
- (IBAction)postButtonEvent:(id)sender {
NSLog(@"%s", __FUNCTION__);
// 发出通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"Test" object:nil];
}
// TestViewController.m
@interface TestViewController ()
@property(nonatomic, copy) NSString *string;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.string = @"TestViewController";
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 第一种方式
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testFunction) name:@"Test" object:nil];
// 第二种方式
// __weak typeof(self) weakSelf = self;
// [[NSNotificationCenter defaultCenter] addObserverForName:@"Test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
// NSLog(@"%s", __FUNCTION__);
//
// [weakSelf testFunction];
// }];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 移除通知
// [[NSNotificationCenter defaultCenter] removeObserver:self name:@"Test" object:nil];
}
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
- (void)testFunction {
NSLog(@"%s", __FUNCTION__);
NSLog(@"string: %@", self.string);
}
@end
3. 测试结果
// 第一种方式添加通知的测试结果
-[ViewController toTestButtonEvent:]
-[TestViewController dealloc]
-[ViewController postButtonEvent:]
-[ViewController toTestButtonEvent:]
-[TestViewController dealloc]
-[ViewController postButtonEvent:]
由测试结果可见,通过 addObserver:selector:name:object: 方式添加的通知,在 TestViewController 释放后,TestViewController 不会收到通知,也不会崩溃。
// 第二种方式添加通知的测试结果
-[ViewController toTestButtonEvent:]
-[TestViewController dealloc]
-[ViewController postButtonEvent:]
-[TestViewController viewWillAppear:]_block_invoke
-[ViewController toTestButtonEvent:]
-[TestViewController dealloc]
-[ViewController postButtonEvent:]
-[TestViewController viewWillAppear:]_block_invoke
-[TestViewController viewWillAppear:]_block_invoke
如果通过 addObserverForName:object:queue:usingBlock: 方式添加的通知,即使 TestViewController 已经被释放,仍然能够收到通知。由于 block 内使用的是 self 的弱引用对象,向空对象传递消息并不会导致崩溃。
然而从另一个方面考虑,这个 block 由于被 NSNotificationCenter 引用,无法被释放,造成内存泄漏。
4. 官方文档
其实,如果查看官方文档,就可以知道上文的测试结果并不精确。上文中只测试了 iOS 13 环境下 NSNotificationCenter 的表现,并没有对更早的版本进行测试。
搬运一下官方文档:
addObserver:selector:name:object:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call removeObserver:name:object: before observer or any object passed to this method is deallocated.
当处于 iOS 9.0 + 版本,通过该方法添加的通知,可以没必要在对象释放时移除通知。但是低于 iOS 9.0 版本时,就应该手动移除通知。
addObserverForName:object:queue:usingBlock:
To unregister observations, you pass the object returned by this method to removeObserver:. You must invoke removeObserver: or removeObserver:name:object:before any object specified by
addObserverForName:object:queue:usingBlock:
is deallocated.
使用该方法添加的通知,都必须手动移除。
5. 结论
- 当 iOS 9.0+ 时,不移除通知不会崩溃;当低于 iOS 9.0 时,需要手动移除通知。
- 使用 addObserverForName:object:queue:usingBlock: 添加的通知,即使不移除,也不会崩溃,但是造成内存泄漏。