来源:JamesYu

链接:http://www.jianshu.com/p/a4d519e4e0d5

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

NSNotificationCenter

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

正确姿势之remove

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

首先,向NSNotificationCenter中addObserver后,并没有对这个对象进行引用计数加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];

});

}

总结

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

NSNotificationCenter 使用姿势详解的更多相关文章

  1. iOS NSNotificationCenter 使用姿势详解

    最近在做平板的过程中,发现了一些很不规范的代码.偶然修复支付bug的时候,看到其他项目代码,使用通知的地方没有移除,我以为我这个模块的支付闪退是因为他通知没有移除的缘故.而在debug和看了具体的代码 ...

  2. Linux内核ROP姿势详解(二)

    /* 很棒的文章,在freebuf上发现了这篇文章上部分的翻译,但作者貌似弃坑了,顺手把下半部分也翻译了,原文见文尾链接 --by JDchen */ 介绍 在文章第一部分,我们演示了如何找到有用的R ...

  3. SpringBoot系列教程JPA之query使用姿势详解之基础篇

    前面的几篇文章分别介绍了CURD中的增删改,接下来进入最最常见的查询篇,看一下使用jpa进行db的记录查询时,可以怎么玩 本篇将介绍一些基础的查询使用姿势,主要包括根据字段查询,and/or/in/l ...

  4. SpringBoot系列教程JPA之delete使用姿势详解

    原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿 ...

  5. NSNotificationCenter 的使用详解

    通常我们在 iOS 中发生什么事件时该做什么是由 Delegate 实现的,例如 View 加载完后会触发 viewDidLoad.Apple 还为我们提供了另一种通知响应方式,那就是 NSNotif ...

  6. 如约而至,Java 10 正式发布! Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势 努力的孩子运气不会太差,跌宕的人生定当更加精彩 优先队列详解(转载)

    如约而至,Java 10 正式发布!   3 月 20 日,Oracle 宣布 Java 10 正式发布. 官方已提供下载:http://www.oracle.com/technetwork/java ...

  7. SQL注入攻防入门详解

    =============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...

  8. [转]SQL注入攻防入门详解

    原文地址:http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html =============安全性篇目录============ ...

  9. 转载]IOS LBS功能详解[0](获取经纬度)[1](获取当前地理位置文本 )

    原文地址:IOS LBS功能详解[0](获取经纬度)[1](获取当前地理位置文本作者:佐佐木小次郎 因为最近项目上要用有关LBS的功能.于是我便做一下预研. 一般说来LBS功能一般分为两块:一块是地理 ...

随机推荐

  1. Fedora 14 x64 试用手记

    欢迎大家给我投票: http://2010blog.51cto.com/350944 刊登在: http://os.51cto.com/art/201011/235506.htm FC14桌面使用体验 ...

  2. 【转】XML之命名空间的作用(xmlns)

    原文链接:http://blog.csdn.net/zhch152/article/details/8191377 命名空间的作用,下面的内容是转载的,大家可以看看:   问题的出现:XML的元素名字 ...

  3. ASP.NET自定义控件入门Demo

    最近看了MSDN关于自定义控件的介绍,根据官方的文档,自己学着做了一个简单的Demo给需要的朋友参考. ASP.NET 源生的TextBox是不带Label标签的,这里我要实现的是创建一个带Label ...

  4. quora 中有关angular与emberjs的精彩辩论

    原贴地址,要注册才能看,这里只有国人翻译的一部分内容 本文源自于Quora网站的一个问题,作者称最近一直在为一个新的Rails项目寻找一个JavaScript框架,通过筛选,最终纠结于Angular. ...

  5. MYSQL数据库性能调优之七:其他(读写分离、分表等)

    一.分表 水平划分 垂直划分 二.读写分离 三.选择合理的数据类型 特别是主键 四.文件.图片等大文件使用文件系统存储 五.数据库参数配置 注意:max_connections最大连接数一般设置在10 ...

  6. POJ 3449 Geometric Shapes(判断几个不同图形的相交,线段相交判断)

    Geometric Shapes Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 1243   Accepted: 524 D ...

  7. AutoCAD.NET 不使用P/Invoke方式调用acad.exe或accore.dll中的接口(如acedCommand、acedPostCommand等)

    使用C#进行AutoCAD二次开发,有时候由于C#接口不够完善,或者低版本AutoCAD中的接口缺少,有些工作不能直接通过C#接口来实现,所以需要通过P/Invoke的方式调用AutoCAD的其他DL ...

  8. JS中如何判断null、undefined与NaN

    1.判断undefined: <span style="font-size: small;">var tmp = undefined; if (typeof(tmp)  ...

  9. 检测iOS的APP性能的一些方法

    首先如果遇到应用卡顿或者因为内存占用过多时一般使用Instruments里的来进行检测.但对于复杂情况可能就需要用到子线程监控主线程的方式来了,下面我对这些方法做些介绍: Time Profiler ...

  10. CodeForces 710E Generate a String (DP)

    题意:给定 n,x,y,表示你要建立一个长度为 n的字符串,如果你加一个字符要花费 x时间,如果你复制前面的字符要花费y时间,问你最小时间. 析:这个题,很明显的DP,dp[i]表示长度为 i 的字符 ...