NSNotificationCenter深入研究

作者: Yasin的简书 | 来源:发表于2016-02-02 15:05 被阅读1793次

    最近看到一段有趣的代码

          if ([[UIDevice currentDevice].systemVersion floatValue] < 9.0) {
                __weak typeof(self) weakSelf;
                [[NSNotificationCenter defaultCenter] addObserverForName:kMEChangeNotification
                                                                  object:nil
                                                                   queue:[NSOperationQueue mainQueue]
                                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                                  [weakSelf doSomeThing];
                                                              }];
            } else {
                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomeThing) name:kMEChangeNotification object:nil];
            }
    

    一开始看到就猜想难道不同的系统接受通知的姿势不一样?但是之前一直都在用addObserver,也没出现过问题啊,感觉这串代码是多此一举,就没在管。后来有一次因为没有removeObserver引发了崩溃,才发现这串代码是有深意的。最近整理了一下,留作笔记,因为这里面特别绕

    规则只有一个,无论什么时候都不可以打破

    规则:addObserverForNameblock里面必须使用weakSelf,否则会造成循环引用导致当前类无法释放

    注意事项

    • 注意事项一:doSomeThing里面如果要刷新UI,最好是回调主线程。
      因为通知的接收所在的线程是基于发送通知所在的线程,如果通知是在主线程发出的,通知的接收也是在主线程,如果通知的发送是在子线程,通知的接收也是在子线程。也就是说如果你不能保证你的通知一定是在主线程发送的,就最好回到主线程刷新UI
    -(void)doSomeThing
    {
            //do something
    
       dispatch_async(dispatch_get_main_queue(), ^{
           // UI
       });
    }
    
    • 注意事项二:通知的移除要调用各自的移除方法
      addObserverForNameaddObserver通知的移除方法是不一样的
      addObserver通知的移除方法是[[NSNotificationCenter defaultCenter]removeObserver:self],这是最常见的,然而对于addObserverForName是没有用的,
      addObserverForName通知的移除方法是在声明的时候要记录通知变量
    @interface SecondViewController ()
    {
        id notificationObserver;
    }
    //或者
    //@property (nonatomic, strong) id notificationObserver;
    @end
    
     __weak typeof(self) weakSelf;
        notificationObserver = [[NSNotificationCenter defaultCenter]addObserverForName:@"MYNotificationCenter" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
            NSLog(@"addObserverForName接收到通知");
            [weakSelf doSomeThing];
        }];
    

    然后使用[[NSNotificationCenter defaultCenter]removeObserver:notificationObserver];移除

    打破概念特别注意事项

    特别注意事项:通知的移除不一定非要写
    对于addObserverForName:

    __weak typeof(self) weakSelf;
        notificationObserver = [[NSNotificationCenter defaultCenter]addObserverForName:@"MYNotificationCenter" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
            NSLog(@"addObserverForName接收到通知");  //如果没有移除这个通知,每次接到通知时这个NSLog都会输出
            [weakSelf doSomeThing]; //如果没有移除这个通知,如果是直接使用self,则这个通知所在NSObject或者ViewController都不会释放,如果是使用weakSelf,则这个通知所在NSObject或者ViewController都会释放,就不会再继续调用doSomeThing方法
        }];
    

    对于addObserver:
    这里要分ViewController和普通NSObject两个说起
    ViewController:在调用ViewController的dealloc的时候,系统会调用[[NSNotificationCenter defaultCenter]removeObserver:self]方法,所以如果是在viewDidLoad中使用addObserver添加监听者的话可以省掉移除。当然,如果是在viewWillAppear中添加的话,那么就要在viewWillAppear中自己移除,而且,最好是使用[[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:nil];而非[[NSNotificationCenter defaultCenter]removeObserver:self],否则很有可能你会把系统的其他通知也移除了

    普通NSObject:在iOS9之后,NSObject也会像ViewController一样在dealloc时调用[[NSNotificationCenter defaultCenter]removeObserver:self]方法,在iOS9之前的不会调用,需要自己写。

    文字开头的那段代码应用场景:在使用类别的时候如果我们添加了通知,那么我们是没有办法在类别里面重写dealloc的,如果不移除通知就会出现野指针,这个时候我们就可以在iOS9以上使用addObserver,将通知的移除交给系统,iOS9一下使用addObserverForName+weakSelf,虽然通知依然存在,但是不会调用doSomeThing方法(不要直接在block里面写处理过程啊)。

    ViewController、NSObject、Notification释放图表(YES-释放).png

    最后附上测试demo下载

    再总结一下

    当你的通知没办法注销的时候,iOS9以下addObserverForName可以不注销通知的情况下(通知没释放)释放监听者,iOS9以上使用addObserver可以不手动注销通知情况下释放通知和监听者,涉及到内存使用问题。关键在于没办法手动注销通知的情况下,比如你的扩展里面使用了通知,但是扩展不支持调用dealloc.

    相关文章

      网友评论

      • bigParis:而非[[NSNotificationCenter defaultCenter]removeObserver:self],否则很有可能你会把系统的其他通知也移除了, 会吗?
        Yasin的简书:@bigParis 看情况了
        bigParis:@Yasin的简书 不用通知就只能用callback了吧?
        Yasin的简书:理论上是会的,不过我没遇到过,毕竟我不太喜欢用通知
      • 马铃薯蜀黍:哎么诶啊可
      • 05928c0c7c63:刷新界面完全可以写在通知方法里,
      • 05928c0c7c63:感觉forName没什么卵用啊
        Yasin的简书:@樱空释的爱1 当你的通知没办法注销的时候,iOS9一下addObserverForName可以不注销通知的情况下释放监听者,iOS9以上使用addObserver可以不手动注销通知情况下释放通知和监听者,涉及到内存使用问题。关键在于没办法手动注销通知的情况下,比如你的扩展里面使用了通知,但是扩展不支持调用dealloc.

      本文标题:NSNotificationCenter深入研究

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