NSNotification观察者移除问题的探究

前言

最近需求一直挺多的,所以日子过的也就比较“充实”,就在前天偶然看到一个“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];
    }
}

你可能感兴趣的:(NSNotification观察者移除问题的探究)