iOS NSNotificationCenter 使用姿势详解
最近在做平板的过程中,发现了一些很不规范的代码。偶然修复支付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];
});
}
总结
通知平常使用的知识点差不多就这么多。希望对大家有帮助。最后,代码一定要养成良好的习惯,该移除的还是要移除。
iOS NSNotificationCenter 使用姿势详解的更多相关文章
- NSNotificationCenter 使用姿势详解
来源:JamesYu 链接:http://www.jianshu.com/p/a4d519e4e0d5 最近在做平板的过程中,发现了一些很不规范的代码.偶然修复支付bug的时候,看到其他项目代码,使用 ...
- iOS 单元测试之XCTest详解(一)
iOS 单元测试之XCTest详解(一) http://blog.csdn.net/hello_hwc/article/details/46671053 原创blog,转载请注明出处 blog.csd ...
- iOS学习之UINavigationController详解与使用(一)添加UIBarButtonItem
http://blog.csdn.net/totogo2010/article/details/7681879 1.UINavigationController导航控制器如何使用 UINavigati ...
- IOS—UITextFiled控件详解
IOS—UITextFiled控件详解 //初始化textfield并设置位置及大小 UITextField *text = [[UITextField alloc]initWithFrame:CGR ...
- [转]iOS学习之UINavigationController详解与使用(三)ToolBar
转载地址:http://blog.csdn.net/totogo2010/article/details/7682641 iOS学习之UINavigationController详解与使用(二)页面切 ...
- IOS 友盟使用详解
IOS 友盟使用详解 这篇博客将会详细介绍友盟的使用,希望对博友们有所帮助. 首先我们在浏览器上搜索友盟. 在这里我们选择官网这个,进去友盟官网后我们按照下图进行选择. 接下来选择如下图 Next 这 ...
- iOS原生地图开发详解
在上一篇博客中:http://my.oschina.net/u/2340880/blog/414760.对iOS中的定位服务进行了详细的介绍与参数说明,在开发中,地位服务往往与地图框架结合使用,这篇博 ...
- [转]iOS学习之UINavigationController详解与使用(二)页面切换和segmentedController
转载地址:http://blog.csdn.net/totogo2010/article/details/7682433 iOS学习之UINavigationController详解与使用(一)添加U ...
- iOS中—触摸事件详解及使用
iOS中--触摸事件详解及使用 (一)初识 要想学好触摸事件,这第一部分的基础理论是必须要学会的,希望大家可以耐心看完. 1.基本概念: 触摸事件 是iOS事件中的一种事件类型,在iOS中按照事件划分 ...
随机推荐
- o2s【我】
//预处理对象转String方法 private String o2s(Object o) { if(o!=null&&!"".equals(o)&& ...
- 中标麒麟QT5编译出现:cannot find -lGL 和 collect2:error:ld returned 1 exit status 错误
sudo yum install mesa-libGL-devel mesa-libGLU-devel
- Sqoop2 将hdfs中的数据导出到MySQL
1.进入sqoop2终端: [root@master /]# sqoop2 2.为客户端配置服务器: sqoop:000> set server --host master --port 120 ...
- WINDOWS配置WSUS。
wsus的注册表文件! Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINESOFTWAREPoliciesMicrosoftWindows ...
- jenkins--master/slave模式---master是容器版---slave是非容器版
实验架构: 192.168.0.96 gitlab 192.168.0.97 jenkins.tomcat 192.168.0.98 harbor.docker集群.git.jdk.maven 1.先 ...
- 【kubectl 原理】kubectl 命令执行的时候究竟发生了什么(kubectl、apiserver、etcd)
参考: https://www.yangcs.net/posts/what-happens-when-k8s/ 总而言之,kubectl命令执行的时候,先在本地封装请求,然后过kube-apiserv ...
- 利用VisualSVN修改配置库名称
相信大家都听说过SVN的大名,至于它的用途以及如何安装不在本文范围内,这里主要讲解如何利用VisualSVN来更改配置库的名称,前提是你的SVN服务必须用VisualSVN搭建,网上几乎没有这方面的文 ...
- 【深度学习与神经网络】深度学习的下一个热点——GANs将改变世界
本文作者 Nikolai Yakovenko 毕业于哥伦比亚大学,目前是 Google 的工程师,致力于构建人工智能系统,专注于语言处理.文本分类.解析与生成. 生成式对抗网络-简称GANs-将成为深 ...
- java多线程实现多客户端socket通信
一.服务端 package com.czhappy.hello.socket; import java.io.IOException; import java.net.InetAddress; imp ...
- PHP mbstring通过多字节字符串扩展处理中文查找、计算问题
最近有个需求有到了mbstring相关的函数进行中文处理,如下: mb_strpos mb_strlen 过程中遇到一点比较奇怪的问题,及在本地环境运行没有问题 但我们生产环境是2台服务器,其中一台正 ...