iOS下KVO使用过程中的陷阱 (转发)
iOS下KVO使用过程中的陷阱
KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应。网上广为流传普及的一个例子是利用KVO检测股票价格的变动,例如这里。这个例子作为扫盲入门还是可以的,但是当应用场景比较复杂时,里面的一些细节还是需要改进的,里面有多个地方存在crash的危险。本文旨在逐步递进深入地探讨出一种目前比较健壮稳定的KVO实现方案,弥补网上大部分教程的不足!
首先,假设我们的目标是在一个UITableViewController内对tableview的contentOffset进行实时监测,很容易地使用KVO来实现为。
在初始化方法中加入:
1
|
[_tableView addObserver: self forKeyPath:@ "contentOffset" options: NSKeyValueObservingOptionNew context: nil ]; |
在dealloc中移除KVO监听:
1
|
[_tableView removeObserver: self forKeyPath:@ "contentOffset" context: nil ]; |
添加默认的响应回调方法:
1
2
3
4
5
|
- ( void )observeValueForKeyPath:( NSString *)keyPath ofObject:( id )object change:( NSDictionary *)change context:( void *)context { [ self doSomethingWhenContentOffsetChanges]; } |
好了,KVO实现就到此完美结束了,拜拜。。。开个玩笑,肯定没这么简单的,这样的代码太粗糙了,当你在controller中添加多个KVO时,所有的回调都是走同上述函数,那就必须对触发回调函数的来源进行判断。判断如下:
1
2
3
4
|
- ( void )observeValueForKeyPath:( NSString *)keyPath ofObject:( id )object change:( NSDictionary *)change context:( void *)context { if (object == _tableView && [keyPath isEqualToString:@ "contentOffset" ]) { |
1
|
[ self doSomethingWhenContentOffsetChanges]; |
1
|
} } |
你以为这样就结束了吗?答案是否定的!我们假设当前类(在例子中为UITableViewController)还有父类,并且父类也有自己绑定了一些其他KVO呢?我们看到,上述回调函数体中只有一个判断,如果这个if不成立,这次KVO事件的触发就会到此中断了。但事实上,若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,或者super-superClass...中,上述处理砍断了这个链。合理的处理方式应该是这样的:
1
2
3
4
5
6
7
8
9
|
- ( void )observeValueForKeyPath:( NSString *)keyPath ofObject:( id )object change:( NSDictionary *)change context:( void *)context { if (object == _tableView && [keyPath isEqualToString:@ "contentOffset" ]) { [ self doSomethingWhenContentOffsetChanges]; } else { [ super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } |
这样就结束了吗?答案仍旧是否定的。潜在的问题有可能出现在dealloc中对KVO的注销上。KVO的一种缺陷(其实不能称为缺陷,应该称为特性)是,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。不要以为这种情况很少出现!当你封装framework开源给别人用或者多人协作开发时是有可能出现的,而且这种crash很难发现。不知道你发现没,目前的代码中context字段都是nil,那能否利用该字段来标识出到底kvo是superClass注册的,还是self注册的?
回答是可以的。我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的kvo,而不是父类中的kvo,避免二次remove造成crash。
=======================================================
原创文章,转载请注明 编程小翁@博客园,邮件zilin_weng@163.com,欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!
=======================================================
iOS下KVO使用过程中的陷阱 (转发)的更多相关文章
- 【原】iOS下KVO使用过程中的陷阱
KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应.网上广为流传普及的一个例子是利用KVO检测股票价格的变动,例如这里.这个 ...
- Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果
目前Android的实现是:有来电时,音乐声音直接停止,铃声直接直接使用设置的铃声音量进行铃声播放. Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果. 如果 ...
- 在Linux下安装PHP过程中,编译时出现错误的解决办法
在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...
- Java 理论和实践: 了解泛型 识别和避免学习使用泛型过程中的陷阱
Brian Goetz (brian@quiotix.com), 首席顾问, Quiotix 简介: JDK 5.0 中增加的泛型类型,是 Java 语言中类型安全的一次重要改进.但是,对于初次使用泛 ...
- windows下创建子进程过程中代码重复执行问题
windows在启动子进程的时候会将主进程文件倒入到子进程中.导入模块就相当于执行这个模块中的代码, 所以第一个print会在主进程中执行一次,又在被导入的过程中在子进程中又执行了一次. p.star ...
- WINDOWS系统下MYSQL安装过程中的注意事项
1.首先MySQL的安装方式有两种:一种是MSI安装方式,很简单就像安装Windows软件一样.另外一种就是ZIP安装方式.这种相对而言比较麻烦.新手推荐MSI安装方式. 安装方式有以下两种: MSI ...
- wpf下datagrid使用过程中需要注意的几点(一)
MainWindow.xaml中的代码如下: <DataGrid CanUserAddRows="False" ItemsSource="{Binding}&quo ...
- win10下安装Wampservice过程中遇到的问题及解决办法
今天在电脑上装Wampserver的时候遇到了几个问题,启动Wampserver无法成功,一直显示橙色.若启动成功Wampserver的图标会显示绿色. 下面的是解决方法 安装 在浏览器中搜索Wamp ...
- 记录下安装ES过程中遇到的错误及解决
1.集群配置 需要修改 cluster.name .node.name .network.host: 0.0.0.0[此处默认localhost].http.port: 9200 只要集群名相同,且机 ...
随机推荐
- python类与对象基本语法
面向对象编程的2个非常重要的概念:类和对象. 对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念--类. 类就相当于制造飞机时的图纸,用它 ...
- 基于WebForm和Bootstrap的权限框架解决方案 一.PQGRID的使用
天天打游戏也不是个事,就写一套权限框架吧,我的要求是即漂亮美观大方上档次,又要实用易用接地气. 按理来说应该先设计数据库在来秀的,但是人生就是这么随意,先搭个框子吧, 这一篇的重点是pqgrid的介绍 ...
- RHEL7对比RHEL6的主要变化
RHEL7和RHEL6的主要变化 RHEL7和RHEL6的主要变化 RHEL7 RHEL6 文件系统 XFS EXT4 内核版本 3.10.x-x系列 2.6.x-x系列 内核名称 Maipo S ...
- Java设计模式:工厂模式
问题提出 Java的工厂模式与现实生活中的工厂的模型是很相似的.工厂是用来做什么?当然是用来生成产品.因此在Java的工厂模式的关键点就是如何描述好产品和工厂这2个角色之间的关系. 下面来仔细描述一下 ...
- socket.io的编程实现
socket.io实例 一.环境要求 客户端需要引用socket.io.js文件 服务端需要按照node环境,以及npm install socket.io用来安装服务端的socket.io 二.客户 ...
- 在ASP dot Net Core MVC中用Controllers调用你的Asp dotnet Core Web API 实现CRUD到远程数据库中,构建你的分布式应用(附Git地址)
本文所有的东西都是在dot Net Core 1.1环境+VS2017保证测试通过. 本文接着上次文章接着写的,不了解上篇文章的可能看着有点吃力.我尽量让大家都能看懂.这是上篇文章的连接http:// ...
- C#图像处理——ImageProcessor
这是个老生常谈的话题,需求实在太多,而且也较简单,写此文也是因为几个月没写技术文章了,权当为下一步开个头.我之前也做过很多此类项目,但是就我自己来说每次处理方式还都不一样,有用OpenCV的,有用Ma ...
- Oracle修改监听端口号1521[转]
在oracle中,默认的监听端口号为1521,一旦有人扫描出这个端口号就会知道此服务器为oracle数据库服务器,存在极其大的安全隐患,在这里,教大家如何修改oracle默认端口号为9999: 1.查 ...
- cas错误:org.jasig.cas.client.validation.TicketValidationException: No principal was found in the response from the CAS server.
这个问题困扰了我好几天,最终被这个哥们解决了,具体请参考:http://www.oschina.net/question/252484_149958?sort=time
- CF798 C. Mike and gcd problem
/* CF798 C. Mike and gcd problem http://codeforces.com/contest/798/problem/C 数论 贪心 题意:如果一个数列的gcd值大于1 ...