前言
最近需求一直挺多的,所以日子过的也就比较“充实”,就在前天偶然看到一个“NSNotification不移除在iOS9.0前后有什么区别的问题”引起了我的兴趣。虽然在使用到NSNotification的时候,我个人都有手动移除通知监听者的编程习惯,因为我知道unsafe_unretained引用当被引用对象在释放时不会自动被置为nil,造成野指针,可能会crash,但我不清楚会和系统版本有关。搜了一下资料发现在UIViewController中使用和在NSObject中使用及在不用系统版本中还是有区别的,于是我决定写个场景Demo去探究下。
一、在UIViewController中
模拟场景
有两个控制器ControllerA和ControllerB,A中有两个按钮nextButton和sendButton,点击nextButton页面push到ControllerB,点击sendButton发送一个名为“CrashNotification”的通知;ControllerB中只有注册一个名为“CrashNotification”的通知的监听者和一个UILabel。
代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *nextButton = [UIButton buttonWithType:UIButtonTypeCustom];
[nextButtonsetTitle:@"Next" forState:UIControlStateNormal];
[nextButtonsetTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[nextButtonaddTarget:self action:@selector(nextButtonAction) forControlEvents:UIControlEventTouchUpInside];
nextButton.frame=CGRectMake(100,100,200,44);
nextButton.backgroundColor = [UIColor grayColor];
[self.viewaddSubview:nextButton];
UIButton *sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
[sendButtonsetTitle:@"send" forState:UIControlStateNormal];
[sendButtonsetTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[sendButtonaddTarget:self action:@selector(sendButtonAction) forControlEvents:UIControlEventTouchUpInside];
sendButton.frame=CGRectMake(100,200,200,44);
sendButton.backgroundColor = [UIColor grayColor];
[self.viewaddSubview:sendButton];
}
- (void)nextButtonAction {
ControllerB *vc = [ControllerB new];
[self.navigationController pushViewController:vc animated:YES];
}
- (void)sendButtonAction {
[[NSNotificationCenter defaultCenter] postNotificationName:@"CrashNotification" object:nil userInfo:nil];
}
ControllerB中代码:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapNoti:) name:@"CrashNotification" object:nil];
self.view.backgroundColor = [UIColor yellowColor];
self.label = [[UILabel alloc] init];
self.label.frame = CGRectMake(100, 100, 200, 44);
self.label.textColor = [UIColor blackColor];
[self.view addSubview:self.label];
}
- (void)dealloc {
NSLog(@"ControllerB dealloc");
}
当我从ControllerA push到ControllerB,然后pop返回到ControllerA,发现控制台有输出ControllerB dealloc方法的log,点击ControllerA中的sendButton来发送通知,不会出现crash情况,不论测试机iOS系统版本是多少,都是正常的。
原因
不移除不会出现野指针,虽然对观察者对象进行unsafe_unretained引用,当被引用的对象在释放时不会自动被置为nil,但是由于controller在释放会走dealloc方法,系统会调用[[NSNotificationCenter defaultCenter]removeObserver:self],来移除掉注册的通知,自然就不会出现野指针了,所以如果是在viewDidLoad中使用addObserver添加监听者的话可以省掉移除。
二、在NSObject中
模拟场景
有两个控制器ControllerA和ControllerB和一个继承自NSObject的ClassA,ControllerA中有两个按钮nextButton和sendButton,点击nextButton页面push到ControllerB,点击sendButton发送一个名为“CrashNotification”的通知;ControllerB中有一个strong修饰的ClassA的对象属性;ClassA中注册通知“CrashNotification”的监听。
ControllerB中的代码如下
- (void)viewDidLoad {
[super viewDidLoad];
self.a = [[AClass alloc] init];
}
- (void)dealloc {
NSLog(@"ControllerB dealloc");
}
ClassA中的代码如下
- (instancetype)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapNoti:) name:@"test" object:nil];
}
return self;
}
- (void)tapNoti:(NSNotification *)noti {
NSLog(@"lalala~接收到通知了");
}
- (void)dealloc {
NSLog(@"ClassA dealloc");
}
当我从ControllerA push到ControllerB,然后pop返回到ControllerA,发现控制台有输出ControllerB 和ClassA的 dealloc方法的log,点击ControllerA中的sendButton来发送通知,在iOS9.0之前系统运行会出现crash,在iOS9.0之后系统运行不会出现crash。
原因
在iOS9.0之后,NSObject也会像ViewController一样,在对象释放走dealloc方法的时候调用[[NSNotificationCenter defaultCenter]removeObserver:self],来移除掉注册的通知,就会和在UIViewController中注册通知一样,不会造成野指针了。
但是在iOS9.0之前,系统是不会调用[[NSNotificationCenter defaultCenter]removeObserver:self]方法的,这样,由于观察者对象是unsafe_unretained引用的,NSObject类对象释放时,观察者对象没有被置为nil,这就意味着它变成了野指针,而对野指针发送消息就会导致程序crash了。
三、特殊case(使用[NSNotificationCenter addObserverForName:object:queue:usingBlock]注册通知)
模拟场景
将上面demo中的
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapNoti:) name:@"CrashNotification" object:nil];
换成
_observe = [[NSNotificationCenter defaultCenter] addObserverForName:@"CrashNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
weakSelf.label.text = @"收到了Tap";
}];
@property (nonatomic, weak) id observe;
虽然observe是用weak修饰的,但是在注册的时候涉及到block会导致注册者被系统retain,观察者也就变成了强引用,当观察者对象所在的controller或者NSObject销毁时(走dealloc方法),观察者对象还是存在的,当再次进入这个controller或者创建NSObject对象,observe会再次创建,就会有发送一次通知,多个观察者收到通知,执行多次代码的问题,所以,这种情况一定要移除观察者的,移除时也是有些区别的
- (void)dealloc {
if (_observe) {
[[NSNotificationCenter defaultCenter] removeObserver:_observe];
}
}
网友评论