美文网首页iOS开发
NSNotification观察者移除问题的探究

NSNotification观察者移除问题的探究

作者: J丶k | 来源:发表于2019-03-29 12:02 被阅读0次

    前言

    最近需求一直挺多的,所以日子过的也就比较“充实”,就在前天偶然看到一个“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观察者移除问题的探究

        本文链接:https://www.haomeiwen.com/subject/hxvqbqtx.html