【原】iOS下KVO使用过程中的陷阱

=======================================================

原创文章,转载请注明 编程小翁@博客园,邮件[email protected],欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!

=======================================================

KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应。网上广为流传普及的一个例子是利用KVO检测股票价格的变动,例如这里。这个例子作为扫盲入门还是可以的,但是当应用场景比较复杂时,里面的一些细节还是需要改进的,里面有多个地方存在crash的危险。本文旨在逐步递进深入地探讨出一种目前比较健壮稳定的KVO实现方案,弥补网上大部分教程的不足!

首先,假设我们的目标是在一个UITableViewController内对tableview的contentOffset进行实时监测,很容易地使用KVO来实现为:

在初始化方法中加入:[_tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
在dealloc中移除通知:[_tableView removeObserver:self forKeyPath:@"contentOffset" context:nil];

添加默认的响应回调方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
       [self doSomethingWhenContentOffsetChanges];
}

好了,KVO实现就到此完美结束了,拜拜。。。开个玩笑,肯定没这么简单的,这样的代码太粗糙了,当你在controller中添加多个KVO时,所有的回调都是走同上述函数,那就必须对触发回调函数的来源进行判断。判断如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
    if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
       [self doSomethingWhenContentOffsetChanges];
} }

你以为这样就结束了吗?答案是否定的!我们假设当前类(在例子中为UITableViewController)还有父类,并且父类也有自己绑定了一些其他KVO呢?我们看到,上述回调函数体中只有一个判断,如果这个if不成立,这次KVO事件的触发就会到此中断了。但事实上,若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,或者super-superClass...中,上述处理砍断了这个链。合理的处理方式应该是这样的:

- (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注册的。未完待续...

时间: 2024-10-05 17:13:26

【原】iOS下KVO使用过程中的陷阱的相关文章

Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果

目前Android的实现是:有来电时,音乐声音直接停止,铃声直接直接使用设置的铃声音量进行铃声播放. Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果. 如果要实现这个效果,首先要搞清楚两大问题: 1.来电时的代码主要实现流程. 2.主流音乐播放器在播放过程中,如果有来电,到底在收到了什么事件后将音乐暂停了? 一:来电时的代码主要实现流程 我不是第一研究来电代码的人,网上已经有高手对这个流程剖析过,不是不完全符合我的要求,我参考过的比较有价值的是如下两个

iOS 8 动画执行过程中返回 Crash

之前项目里一直有个iOS8 [UIScrollView(UIScrollViewInternal) _notifyDidScroll] crash的问题,存在了很久,后来复现问题,返现是动画执行过程中执行pop 的问题 场景:在商品详情页加车之后  做了滑动到下面的商品推荐位置, 当动画还没有执行完毕, 就POP回上个页面,就会发生如下Crash 参考文档 https://stackoverflow.com/questions/26103756/uiscrollview-internal-con

WINDOWS系统下MYSQL安装过程中的注意事项

1.首先MySQL的安装方式有两种:一种是MSI安装方式,很简单就像安装Windows软件一样.另外一种就是ZIP安装方式.这种相对而言比较麻烦.新手推荐MSI安装方式. 安装方式有以下两种: MSI的安装包方式(相对简单):ZIP安装方式 选择安装类型:(推荐选择Typical 典型安装方式) Typical:典型安装 Custom:自定义安装 Complete:完全安装 2.MySQL配置 详细配置 detailed configuration 标准配置 standard configura

[原]编译Android源码过程中遇到的问题

编译Android源码的过程参考Android官网介绍: 1.下载Android源码的步骤:https://source.android.com/source/downloading.html 2.编译Android源码的步骤:https://source.android.com/source/building-running.html 下面就是我遇到的一些问题: 1.Compile Android Source时JDK相关的错误: 错误1: target release 1.5 conflic

win10下安装Wampservice过程中遇到的问题及解决办法

今天在电脑上装Wampserver的时候遇到了几个问题,启动Wampserver无法成功,一直显示橙色.若启动成功Wampserver的图标会显示绿色. 下面的是解决方法 安装 在浏览器中搜索Wampserver,点击下载,下载之后安装直接点next,安装完成之后自动启动了Wampserver,启动之后一般都是显示橘黄色. 如图所示   修改默认端口 这是来修改Wampserver的默认端口,点击Wampserver小图标,找到Apache下的httpd.conf文件,用记事本(其他文本编辑器也

MacOS下Express安装过程中遇到的问题

问题描述: 使用nmp install express -g命令全局安装express后,在终端使用express -V命令可以获取到express的版本号,但在引用express的项目运行时,会报缺少express的错误,如下图 解决方案: 在配置文件/etc/profile中添加Node的路径 export NODE_PATH="/usr/local/lib/node_modules" 重新运行/etc/profile source /etc/profile 重启终端,既可正常使用

windows下python-nmap运行过程中出现的问题及解决办法

1)问题描述 在windows下会抛出以下异常. Traceback (most recent call last): File "ftplogin_file.py", line 111, in <module> main() File "ftplogin_file.py", line 109, in main ftpLogin(filepath) File "ftplogin_file.py", line 77, in ftpLog

kubernetes环境下 创建pod过程中 异常信息总结整理

[toc] 1.异常信息:Failed to pull image "spark:0.1": rpc error: code = Unknown desc = repository docker.io/spark not found: does not exist or no pull access 2.异常信息:Error from server (BadRequest): container "xej" in pod "xej-545694f448-j

【原】在Matplotlib绘图过程中设置X轴的刻度和显示文本

使用Matplotlib进行绘图时,当x轴的数据太多的时候,就需要设置x轴的刻度和显示文本,关键代码如下: 绘图结果如下: