美文网首页IOS收藏iOSiOS实战
iOS NSNotificationCenter 使用姿势详解

iOS NSNotificationCenter 使用姿势详解

作者: JamesYu | 来源:发表于2015-01-20 00:00 被阅读64934次

    最近在做平板的过程中,发现了一些很不规范的代码。偶然修复支付bug的时候,看到其他项目代码,使用通知的地方没有移除,我以为我这个模块的支付闪退是因为他通知没有移除的缘故。而在debug和看了具体的代码的时候才发现和这里没有关系。在我印象中,曾经因为没有移除通知而遇到闪退的问题。所以让我很意外,于是写了个demo研究了下,同时来讲下NSNotificationCenter使用的正确姿势。

    NSNotificationCenter

    对于这个没必要多说,就是一个消息通知机制,类似广播。观察者只需要向消息中心注册感兴趣的东西,当有地方发出这个消息的时候,通知中心会发送给注册这个消息的对象。这样也起到了多个对象之间解耦的作用。苹果给我们封装了这个NSNotificationCenter,让我们可以很方便的进行通知的注册和移除。然而,有些人的姿势还是有点小问题的,下面就看看正确的姿势吧!

    正确姿势之remove

    只要往NSNotificationCenter注册了,就必须有remove的存在,这点是大家共识的。但是大家在使用的时候发现,在UIViewControlleraddObserver后没有移除,好像也没有挂!我想很多人可能和我有一样的疑问,是不是因为使用了ARC?在你对象销毁的时候自动置为nil了呢?或者苹果在实现这个类的时候用了什么神奇的方式呢?下面我们就一步步来探究下。

    首先,向NSNotificationCenteraddObserver后,并没有对这个对象进行引用计数加1操作,所以它只是保存了地址。为了验证这个操作,我们来做下代码的测试。

    一个测试类,用来注册通知:

    
    @implementation MRCObject
    
    - (id)init
    {
        if (self = [super init]) {
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
        }
        return self;
    }
    
    - (void)test
    {
        NSLog(@"=================");
    }
    
    - (void)dealloc
    {
        [super dealloc];
    }
    
    @end
    
    

    这个类很简单,就是在初始化的时候,给他注册一个通知。但是在销毁的时候不进行remove操作。我们在VC中创建这个对象后,然后销毁,最后发送这个通知:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        MRCObject *obj = [[MRCObject alloc] init];
        [obj release];
    
        [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
    }
    

    在进入这个vc后,我们发现挂了。。而打印出的信息是:

    2015-01-19 22:49:06.655 测试[1158:286268] *** -[MRCObject test]: message sent to deallocated instance 0x17000e5b0
    

    我们可以发现,向野指针对象发送了消息,所以挂掉了。从这点来看,苹果实现也基本差不多是这样的,只保存了个对象的地址,并没有在销毁的时候置为nil

    这点就可以证明,addObserver后,必须要有remove操作。

    现在我们在UIViewController中注册通知,不移除,看看会不会挂掉。

    - (void)viewDidLoad {
        [super viewDidLoad];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    }
    
    

    首先用navigationController进入到这个页面,然后pop出去。最后点击发送通知的按钮事件:

    - (void)didButtonClicked:(id)sender
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
    }
    

    无论你怎么点击这个按钮,他就是不挂!这下,是不是很郁闷了?我们可以找找看,你代码里面没有remove操作,但是NSNotificationCenter那边已经移除了,不然肯定会出现上面野指针的问题。看来看去,也只能说明是UIViewController自己销毁的时候帮我们暗地里移除了。

    那我们如何证明呢?由于我们看不到源码,所以也不知道有没有调用。这个时候,我们可以从这个通知中心下手!!!怎么下手呢?我只要证明UIViewController在销毁的时候调用了remove方法,就可以证明我们的猜想是对的了!这个时候,就需要用到我们强大的类别这个特性了。我们为NSNotificationCenter添加个类别,重写他的- (void)removeObserver:(id)observer方法:

    - (void)removeObserver:(id)observer
    {
        NSLog(@"====%@ remove===", [observer class]);
    }
    
    

    这样在我们VC中导入这个类别,然后pop出来,看看发生了什么!

    2015-01-19 22:59:00.580 测试[1181:288728] ====TestViewController remove===
    

    怎么样?是不是可以证明系统的UIViewController在销毁的时候调用了这个方法。(不建议大家在开发的时候用类别的方式覆盖原有的方法,由于类别方法具有更高的优先权,所以有可能影响到其他地方。这里只是调试用)。

    以上也提醒我们,在你不是销毁的时候,千万不要直接调用[[NSNotificationCenter defaultCenter] removeObserver:self]; 这个方法,因为你有可能移除了系统注册的通知

    正确姿势之注意重复addObserver

    在我们开发中,我们经常可以看到这样的代码:

    
    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    }
    
    - (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:nil];
    }
    
    

    就是在页面出现的时候注册通知,页面消失时移除通知。你这边可要注意了,一定要成双成对出现,如果你只在viewWillAppear 中 addObserver没有在viewWillDisappear 中 removeObserver那么当消息发生的时候,你的方法会被调用多次,这点必须牢记在心。

    正确姿势之多线程通知

    首先看下苹果的官方说明:

    Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.

    意思很简单,NSNotificationCenter消息的接受线程是基于发送消息的线程的。也就是同步的,因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。下面看个示例代码

    //接受消息通知的回调
    - (void)test
    {
        if ([[NSThread currentThread] isMainThread]) {
            NSLog(@"main");
        } else {
            NSLog(@"not main");
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            //do your UI
        });
    
    }
    
    //发送消息的线程
    - (void)sendNotification
    {
        dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(defaultQueue, ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
        });
    }
    
    

    总结

    通知平常使用的知识点差不多就这么多。希望对大家有帮助。最后,代码一定要养成良好的习惯,该移除的还是要移除。

    相关文章

      网友评论

      • 红烧大鸡腿:楼主,我想问一下为什么在 viewWillAppear 中 addObserver没有在viewWillDisappear 中 removeObserver,在当消息发生的时候,方法会被调用多次?这是什么原理?
      • 名扬丶四海:今天我也遇到类似的问题,在iOS8的系统上,VC控制器不移除通知,不会crash,但是如果是NSObject或者UIView,不移除通知,就会crash.又测试在iOS 9,10,11上,无论是VC还是UIView,NSObject,不移除通知,都不会crash.
        JamesYu:@名扬丶四海 系统后来优化了
      • 蛋壳儿:楼主,我按照你最后的那个代码测试,如果把添加UI的代码,直接放到,not main的那个else分支中,触发后延迟几秒钟也会在界面上显示 添加的UI,你文中的那句(而大家都知道操作UI必须在主线程,不然会出现不响应的情况),应该是苹果建议UI操作应该放到主线程中,不然可能会出现不可预料的情况
      • 云溪_Cloud:个人感觉还是应该在viewDidLoad中注册通知,在dealloc中移除通知。如果在viewWillAppear注册通知而在viewWillDisappear移除通知,虽然也能达到相应的效果,但是左滑和右滑的情况下也会多次调用,从业务逻辑关系上讲,在dealloc移除最好,当然我这是个人建议
        yangjiayu:viewDidLoad中注册,dealloc中移除。
        viewWillAppear注册,viewDidDisAppear移除,这两个方法是对应的
        af4e8a35bc89:但是如果在viewDidLoad中注册通知,在dealloc中移除,这样如果从当前页面跳转到下级页面,再返回当前页面,进行触发通知的操作,竟然就不能发出通知了,不要问我为什么,我是从项目中偶然发现的。不知道阁下有没有遇见过类似情况。
        DSperson:需求很重要。。。
      • sven7:我刚才试了一下,ARC下第一个例子好像不会崩溃啊,貌似ARC在dealloc的时候会自动removeObserve????
        sven7:@JamesYu 是用运行时交换方法么?
        sven7:@JamesYu 啥意思呀?我就这么写的 ARC:
        @Implementation MRCObject

        - (id)init
        {
        if (self = [super init]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"removeTest" object:nil];
        }
        return self;
        }

        - (void)test
        {
        NSLog(@"=================");
        }

        - (void)dealloc
        {
        NSLog(@"MRCObject被释放了");
        }

        @EnD
        JamesYu:@sven7 你确定吗?你用方法替换看看,是不是在dealloc的时候给你 remove了。。。
      • TryToFlyHigher:release后推送通知我竟然没挂....求解.... :fearful:
        吃蘑菇De大灰狼:现在貌似处理过,不会崩溃了
      • 小北风sky:我现在的项目里无法调用
        - (void)viewWillAppear:(BOOL)animated
        - (void)viewWillDisappear:(BOOL)animated
        这2个方法,怎么破。。
        iOS_小胜:@如你所愿lie 有可能被替代了
        小北风sky:@小北风sky 用的github上的WXTabbarcontroller,我说的那两个方法都不执行……好诡异……
        lee_moons:@小北风sky 这个是在页面将要显示的时候自动调用这方法吧?
      • Charles___:在background mode里面post notification是会造成ui刷新 程序crash
      • 7044218a8e85:虽然不懂,感觉写的很叼,mark
      • b9961fbc9d30:没看明白,为什么第一个例子可以证明必须要remove
        BearsG:对层主名字保持高度的好奇心
        TryToFlyHigher:@sabina 他的意思是说add到通知中心的只是self的地址而已 没有引用 所以当release后obj就被释放掉了 所以当对obj推送的时候就造成了野指针 大概是这个意思 不对则欢迎各位亲们指正 共同进步 :blush:
      • supersmalltalk:学习了
      • 貓秋:楼主,我想问下,我pop出去控制器后,我不想让系统自动remove了通知,该怎么做好啊?因为我想pop后却仍然保留其之前的通知数据,感谢。。
      • oneDemo:楼主的需求局限一部分类型,不是全部的通知
      • stillwalking:搞不懂,怎么没人提到在通常情况下是在哪里add ,又在哪里remove?是 viewdidload 和 delloc?求解。
        JamesYu:@KtZhang 一次
        stillwalking:@JamesYu 了解了,remove是在delloc吗?
        JamesYu:@stillwalking 这个是根据需求的,如果你希望这个vc生命周期里面都监听,那就在viewdidiload经常add,如果你希望只有这个页面显示的时候才监听,那就viwillappear里面加
      • iOS开发章鱼哥:@Code_Ninja 赞同. 题主这个是需求决定的,应该注明,否则估计会诱导新人. 加油,题主!
        涅槃简书:确实是要看需求的,估计楼主也没注意到要根据实际需求的做不同的移除姿势
      • JamesYu:@Code_Ninja 这个是需求决定的。因为当这个页面不显示的时候,是不能监听的,不然有些方法可能会导致异常

      本文标题:iOS NSNotificationCenter 使用姿势详解

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