NSnotificationCenter 正确使用姿势, removeObject 探索

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

NSNotificationCenter

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

正确姿势之remove

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

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

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21


@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中创建这个对象后,然后销毁,最后发送这个通知:


1

2

3

4

5

6

7

8


- (void)viewDidLoad {

     [ super  viewDidLoad];

     MRCObject *obj = [[MRCObject alloc] init];

     [obj release];

     [[NSNotificationCenter defaultCenter] postNotificationName:@ "test"  object:nil];

}

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


1

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

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

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

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


1

2

3

4


- (void)viewDidLoad {

     [ super  viewDidLoad];

     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@ "test" object:nil];

}

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


1

2

3

4


- (void)didButtonClicked:(id)sender

{

    [[NSNotificationCenter defaultCenter] postNotificationName:@ "test"  object:nil];

}

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

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


1

2

3

4


- (void)removeObserver:(id)observer

{

    NSLog(@ "====%@ remove===" , [observer class]);

}

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


1

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

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

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

正确姿势之注意重复addObserver

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


1

2

3

4

5

6

7

8

9

10

11


- (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必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。下面看个示例代码


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22


//接受消息通知的回调

- (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];

    });

}

总结

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

copy from JamesYu

时间: 2025-01-22 02:28:54

NSnotificationCenter 正确使用姿势, removeObject 探索的相关文章

二分查值,正确的姿势

04:网线主管 总时间限制:  1000ms 内存限制:  65536kB 描述 仙境的居民们决定举办一场程序设计区域赛.裁判委员会完全由自愿组成,他们承诺要组织一次史上最公正的比赛.他们决定将选手的电脑用星形拓扑结构连接在一起,即将它们全部连到一个单一的中心服务器.为了组织这个完全公正的比赛,裁判委员会主席提出要将所有选手的电脑等距离地围绕在服务器周围放置. 为购买网线,裁判委员会联系了当地的一个网络解决方案提供商,要求能够提供一定数量的等长网线.裁判委员会希望网线越长越好,这样选手们之间的距

高版本jquery尤其是1.10.2的版本设置input radio设置值的最正确的姿势。

$("input:radio[name="analyshowtype"]").attr("checked",false); $("input[name=jizai]:eq(0)").attr("checked",'checked'); $("input[@type=radio][name=sex][@value=1]").attr("checked",true); 以

Gradle的依赖方式——Lombok在Gradle中的正确配置姿势

写过java的都知道,lombok几乎在项目中处于不可或缺的一部分,但是lombok在Gradle的项目中配置并非人人都知道. 很多人在项目依赖中直接这样写 1 compile "org.projectlombok:lombok:1.18.4" 但这样的处理在Gradle 5.0以上被命令禁止了,在4.x的高级版本中编译时也会有对应的告警 12345 The following annotation processors were detected on the compile cla

正确的姿势很重要:该如何执笔

快速入门: 蔡新老师教书法-正确握笔姿势—专辑:<蔡新老师教书法>—在线播放—优酷网,视频高清在线观看 (蔡新老师应该是学的布衣老师的执笔方法:布衣老师的执笔方法_伏羲教育林美娟_新浪博客 ) 理论详解: 布衣的博客(需番羽墙):正确握笔姿势,矫正握姿,写字讨论的部落格 :: 随意窝 Xuite日志 目录如下,包括握笔姿势矫正,写字漂亮技巧等... 出了书<写字基本功>,教学视频,还有其他一些东西,官方网站(需番羽墙): 筆博士實業-最好寫的U-Pen矯姿筆.握筆器:專業正確握筆姿

iOS-----5分钟学会枚举的正确使用姿势-Enumeration宏

前言 Enum,枚举,相信大部分编程语言都有对应的枚举类型,功能可能有多有少,但是枚举最核心的功能是 "规范的定义代码中的状态.状态码.选项". 状态.状态码.选项 什么是状态:同时只能出现一个值(状态码就是他的值),比如这个ScrollView里的枚举: Objective-C 1 2 3 4 5 typedef NS_ENUM(NSInteger, UIScrollViewKeyboardDismissMode) { UIScrollViewKeyboardDismissModeN

.net DataTable 正确排序姿势

关于dataTable中根据列排序正确姿势做个随笔,方便查阅 1 System.Data.DataTable dt = new System.Data.DataTable(); 2 dt.Columns.Add("date", typeof(System.String)); 3 System.Data.DataRow dr = dt.NewRow(); 4 dr["date"] = "2016-09"; 5 System.Data.DataRow

Android线程的正确使用姿势

线程是程序员进阶的一道重要门槛.对于移动开发者来说,"将耗时的任务放到子线程去执行,以保证UI线程的流畅性"是线程编程的第一金科玉律,但这条铁则往往也是UI线程不怎么流畅的主因.我们在督促自己更多的使用线程的同时,还需要时刻提醒自己怎么避免线程失控.除了了解各类开线程的API之外,更需要理解线程本身到底是个什么样的存在,并行是否真的高效?系统是怎么样去调度线程的?开线程的方式那么多,什么样的姿势才正确? 多线程编程之所以复杂原因之一在于其并行的特性,人脑的工作方式更符合单线程串行的特点

阿里巴巴Java开发手册正确学习姿势是怎样的?刷新代码规范认知

很多人都知道,阿里巴巴在2017发布了<阿里巴巴Java开发手册>,前后推出了很多个版本,并在后续推出了与之配套的IDEA插件和书籍. 相信很多Java开发都或多或少看过这份手册,这份手册有7个章节,覆盖了编程规约.异常日志.单元测试.安全规约.MySQL数据库.工程结构以及设计规约等方面. 这份规约可以说是覆盖了Java开发的方方面面,如果还有人没看的话,强烈建议大家好好看看,并且仔细研读. 手册中,有那么一些规则,是比较容易理解的.比如一些变量命名规范,有另外一些规则,是不太容易理解的,背

UITextField限制汉字数量最正确的姿势,解决iOS7下substringToIndex方法导致的崩溃

今天在写一个限制UITextField汉字数量的需求,原以为非常简单的一个需求,在实际开发中遇到了很多问题. 首先,汉字的输入时的联想词在输入到TextFiled时,并不会走 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 这导致了在这里进行长度限制的代码都会失效.此路不通. 经过查找资料,得知