KVO你所不知道的"坑"

一、什么是 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。

时间: 2024-12-25 05:38:57

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

KVO中你所不知道的"坑"

一.什么是 KVO 首先让我们了解一下什么KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应.键值观察Key-Value-Observer就是观察者模式. 观察者模式的定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象.这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的.观察者模式较完美地将目标对象与观察者对象解耦. KVO和KVC没有什么关系,要说有关系的话也就是--K

[平衡树学习笔记]那些你所不知道的鬼畜写法

treap:  (Orz fhq 大神,我曾经以为我会了 treap ,就再也不会写 splay 了,然后我遇上了 lct ) 1 #include <cstdlib> 2 const int sizeOfMemory=10000; 3 template <class type> inline void swap(type & x, type & y) {type t=x; x=y; y=t;} 4 5 namespace treap 6 { 7 struct no

你所不知道的html5与html中的那些事(二)

文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作都可以达到真正的云方式呢?这一系列的问题你是否已经想明白了呢? 本系列文章将为您一一解答你所不知道的关于html5与html中的那些事;具体会包括如:html5新的理念与想法,html5的新标签的用意与具体开发中场景应用,html5与css3的感情经历(用法搭配),包括html5的父亲html的一些

你所不知道的html5与html中的那些事(一)

分类: Web开发 文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作都可以达到真正的云方式呢?这一系列的问题你是否已经想明白了呢? 本系列文章将为您一一解答你所不知道的关于html5与html中的那些事;具体会包括如:html5新的理念与想法,html5的新标签的用意与具体开发中场景应用,html5与css3的感情经历(用法搭配),包括html5

JavaScript你所不知道的困惑(2)

困惑一: var obj1 = new Object(); var obj2 = obj1; obj1.name = "阳光小强"; alert(obj2.name); //输出结果:阳光小强 JavaScript中的5个基本类型:Undefined.Null.Boolean.Number和String都是按值访问的,可以操作保存在变量中的实际的值,内存空间如下: var num1 = 5; var num2 = num1; 引用类型的值是保存在内存中的对象,JavaScript不允许

JavaScript你所不知道的困惑(1)

困惑一: 先看一个例子: function test(){ message = "hi"; } test(); alert(message); 会输出字符串"hi" 在函数内部使用var定义的变量是局部变量,省略var操作符的变量是全局变量. 困惑二: alert(undefined == null) 结果是"true" 我们知道在js中分为基本类型和引用类型,基本类型包括number.string.boolean.undefined.null.

你所不知道的html5与html中的那些事(四)——文本标签

文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作都可以达到真正的云方式呢?这一系列的问题你是否已经想明白了呢? 本系列文章将为您一一解答你所不知道的关于html5与html中的那些事;具体会包括如:html5新的理念与想法,html5的新标签的用意与具体开发中场景应用,html5与css3的感情经历(用法搭配),包括html5的父亲html的一些

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

文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作都可以达到真正的云方式呢?这一系列的问题你是否已经想明白了呢? 本系列文章将为您一一解答你所不知道的关于html5与html中的那些事;具体会包括如:html5新的理念与想法,html5的新标签的用意与具体开发中场景应用,html5与css3的感情经历(用法搭配),包括html5的父亲html的一些

你所不知道的SQL Server数据库启动过程(用户数据库加载过程的疑难杂症)

前言 本篇主要是上一篇文章的补充篇,上一篇我们介绍了SQL Server服务启动过程所遇到的一些问题和解决方法,可点击查看,我们此篇主要介绍的是SQL Server启动过程中关于用户数据库加载的流程,并且根据加载过程中所遇到的一系列问题提供解决方案. 其实SQL Server作为微软的一款优秀RDBMS,它启动的过程中,本身所带的那些系统库发生问题的情况相对还是很少的,我们在平常使用中,出问题的大部分集中于我们自己建立的用户数据库. 而且,相对于侧重面而言,其实我们更关注的是我们自己建立的用户数