一、什么是 KVO

  首先让我们了解一下什么KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应。键值观察Key-Value-Observer就是观察者模式。

  观察者模式的定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。

  KVO和KVC没有什么关系,要说有关系的话也就是--KVO同KVC一样都依赖于Runtime的动态机制.

  在WPF中有一种双向绑定机制,如果数据模型修改了之后会立即反映到UI视图上,类似的还有如今比较流行的基于MVVM设计模式的前端框架。其实在ObjC中原生就支持这种机制,它叫做Key Value Observing(简称KVO)。KVO其实是一种观察者模式,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO。

二、怎么实现 KVO

  • 注册
1 //keyPath就是要观察的属性值
2 //options给你观察键值变化的选择
3 //context方便传输你需要的数据
4 -(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
5 options:(NSKeyValueObservingOptions)options context:(void *)context;
  • 实现方法
1 //change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。
2 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
3
4 change:(NSDictionary *)change context:(void *)context
  • 移除
1  //移除
2 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

三、 KVO底层实现分析

系统实现KVO有以下几个步骤:

  当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为B。

  在派生类B中重写类A的setter方法,B类在被重写的setter方法中实现通知机制。

  类B重写会 class方法,将自己伪装成类A。类B还会重写dealloc方法释放资源。

  系统将所有指向类A对象的isa指针指向类B的对象。

通俗一点的解释是:当注册观察者的时候做的哪些事情:

  1.动态的创建一个叫NSKVONotifying_Person的子类

   2.更改之前类的 isa指针为子类

   3.传入一堆参数 1.监听者(将来调用observeValueForKeyPath)  2.keypath(决定了重写哪个set方法)  3.枚举(决定传哪些给你) 4.携带参数

   4.根据keypath 重写子类的set方法

1     //其实在子类的set方法中是实现了下面三步
2 [super setWeight:weight];
3
4 //这两个方法会调用监听者的监听者方法
5 [self willChangeValueForKey:@"weight"];
6 [self didChangeValueForKey:@"weight"];

   5.在子类的set方法中  根据枚举 保存所有的属性值  然后调用父类的set方法 然后调用监听者的observeValueForKeyPath... 把对应的值传出去通知监听者发生了事情。所以不能依靠isa指针来确定对象是否是一个类的成员。应该使用class方法来确定对象实例的类。

四、KVO使用陷阱介绍:

  首先,看一下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 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
3 change:(NSDictionary *)change context:(void *)context
5 {
7 [self doSomethingWhenContentOffsetChanges];
9 }

  通常的写法已经完成,但是当你在controller中添加多个KVO时,所有的回调都是走同上述函数,那就必须对触发回调函数的来源进行判断。判断如下:

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

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

1  - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
2 change:(NSDictionary *)change context:(void *)context
3 {
4 if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
5 [self doSomethingWhenContentOffsetChanges];
6 } else {
7 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
8 }
9 }

  还有潜在的问题有可能出现在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。

KVO中你所不知道的"坑"的更多相关文章

  1. JavaScript中你所不知道的Object(二)--Function篇

    上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...

  2. JavaScript中你所不知道的Object(一)

    Object实在是JavaScript中很基础的东西了,在工作中,它只有那么贫瘠的几个用法,让人感觉不过尔尔,但是我们真的了解它吗? 1. 当我们习惯用 var a = { name: 'tarol' ...

  3. Go基础之--位操作中你所不知道的用法

    之前一直忽略的就是所有语言中关于位操作,觉得用处并不多,可能用到也非常简单的用法,但是其实一直忽略的是它们的用处还是非常大的,下面先回顾一下位操作符的基础 位操作符 与操作:&1 & ...

  4. 前端开发 CSS中你所不知道的伪类与伪元素的区别--摘抄

    做过前端开发的人都熟悉伪类与伪元素,而真正能够彻底了解这二者的区别的人并不多.伪类与伪元素确实很容易混淆. 伪元素主要是用来创建一些不存在原有dom结构树种的元素,例如:用::before和::aft ...

  5. Visual Studio中你所不知道的智能感知

    在Visual Studio中的智能感知,相信大家都用过.summary,param,returns这几个相信很多人都用过的吧.那么field,value等等这些呢. 首先在Visual Studio ...

  6. Android中Context详解 ---- 你所不知道的Context

    转自:http://blog.csdn.net/qinjuning/article/details/7310620Android中Context详解 ---- 你所不知道的Context 大家好,  ...

  7. 你所不知道的html5与html中的那些事第三篇

    文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作 ...

  8. Android中Context详解 ---- 你所不知道的Context(转)

    Android中Context详解 ---- 你所不知道的Context(转)                                               本文出处 :http://b ...

  9. 你所不知道的html5与html中的那些事(三)

    文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作 ...

随机推荐

  1. iOS-关于一些手势冲突问题(scrollView 嵌套 tableView)

    简单说下关于开发中容易遇到的父试图添加手势与子试图点击事件冲突,UIScrollView 嵌套 UIScrollView . UIScrollView 嵌套 UITableView的情况手势冲突问题: ...

  2. Ajax 调用案例及错误捕捉

    function postFunc() { var scoreResultStr = readyData(); $.ajax({ type: "post", url: " ...

  3. HTML5大数据可视化效果(一)彩虹爆炸图

    前言 25年过去了,Brooks博士著名的“没有银弹”的论断依旧没有被打破.HTML5也是一样.但这并不妨碍HTML5是一个越来越有威力的“炸蛋”:发展迅速.势不可挡.随着HTML5技术的普及,用HT ...

  4. 移动端开发需要加的meta

    移动端开发需要加的meta和常用的css3媒体查询样式,移动开发中头部要加的一些常用meta. <meta name="viewport" content="ini ...

  5. 洛谷——P3003 [USACO10DEC]苹果交货Apple Delivery

    P3003 [USACO10DEC]苹果交货Apple Delivery 这题没什么可说的,跑两遍单源最短路就好了 $Spfa$过不了,要使用堆优化的$dijkstra$ 细节:1.必须使用优先队列+ ...

  6. Luogu P1540 机器翻译

    思路 大水题,只需要静下心来模拟就行.我一开始做的时候,首先想到滚动数组但是写完之后发现并不符合题目要求.题目要求新加入的单词作为最新的,在时间上属于最后一个.但是如果用滚动数组的话,新加入的单词就成 ...

  7. 第十二节:Web爬虫之MongoDB数据库安装与数据存储

    MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功 ...

  8. 09.C语言:预处理(宏定义)、字节序、地址对齐

    一.预处理 预处理 gcc -E Hello.c -o hello.i 编译 gcc -S hello.i -o hello.s 汇编 gcc -c hello.s -o hello.o 链接 gcc ...

  9. 洛谷 1821 [USACO07FEB]银牛派对Silver Cow Party

    [题解] 其实解法 #include<cstdio> #include<cstring> #include<algorithm> #define LL long l ...

  10. Uva 10730 Antiarithmetic?

    uva 10730 题意:给出一列数字,如果其中存在长度大于等于3的等差数列,输出no,不存在就输出yes 这道题标定了数列长度n,而且这n个数数据范围是从0到n-1,没有相同的数,这就给我们枚举提供 ...