KVO
、KVC
的实现原理与应用
各种名词:
KVO
: key value observing
KVC
: key value Coding
KVO
是什么
KVO
是OC
对观察者设计模式
的一种实现方式,是以非正式协议(Category)的形式定义在NSObject
中。
当一个被观察的对象(如类A
)的某个属性放生改变时,对象会获得通知,并作出了相应的处理。
在MVC
设计架构下,KVO
是一种理想的Model
、View
之间的通讯机制。例如,在控制器中创建ModelA
的观察者,当ModelA
的某个属性发生变化时,可以通过KVO
使得控制器收到通知,并作出变化。
KVO
的实现原理
举个栗子,当观察对象类ClassA
的propertyX
时,KVO
机制会动态的创建ClassA
的子类,并且为这个新的子类重现被观察的属性propertyX
的keyPath
的setter
方法。当propertyX
发生改变时,会调用setter
方法,这时候,setter
做了一件事情,那就是通知观察者该属性改变的状态。
那么,iOS
是如何动态创建ClassA
的呢?
Apple
使用了isa-swizzling
来实现KVO
的动态创建。当观察ClassA
时,KVO
会动态的创建一个新的 名为NSKVONotifying_ClassA
类,并重写了setter
方法,该方法会负责在调用原setter
方法之前和之后通知所有观察对象属性值的更改情况。
NSNotifying_ClassA
类:在这个过程中,被观察的isa
指针(即Runtime
中指向对象的类的指针)从指向的是原来的ClassA
类,被KVO
机制修改为指向NSNotifying_ClassA
,用来实现当前类的属性值改变监听。
当我们手动的创建了NSNotifying_ClassA
类,那么就会使得系统运行到注册KVO
的那段代码时崩溃。- 子类的setter方法剖析:查看
NSObject
的两个方法willChangeValueForKey:
、didChangevlueForKey:
,在存取数值的前后分别会调用这两个方法,分别在属性改变前和改变后进行调用。
KVO
为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:
-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO在调用存取方法之前总调用
[super setValue:newName forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO在调用存取方法之后总调用
}
观察者观察的是属性,只有遵循了KVO
变更属性值的方式才会执行KVO
的回调方法,例如是否执行了setter
方法、或者是否使用了KVC
赋值。
如果赋值的方式没有通过setter
方法,那么就只是改变值,不会发出通知。例如,以下方式不会触发KVO
机制。
_name = @"newName";
KVO
实现步骤
- 注册观察者,实施监听
//第一个参数observer:观察者 (这里观察self.myKVO对象的属性变化)
//第二个参数keyPath: 被观察的属性名称(这里观察self.myKVO中num属性值的改变)
//第三个参数options: 观察属性的新值、旧值等的一些配置(枚举值,可以根据需要设置,例如这里可以使用两项)
//第四个参数context: 上下文,可以为kvo的回调方法传值(例如设定为一个放置数据的字典)
[self.myKVO addObserver:self forKeyPath:@"num" options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
- 在回调方法中处理属性发生的变化
当属性(keyPath)的值发生变化时,收到通知,调用以下方法:
// keyPath:属性名称
// object:被观察的对象
// change:变化前后的值都存储在change字典中
// context:注册观察者时,context传过来的值
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
}
- 移除观察者
KVC
与KVO
的不同
KVC
是一个非正式的Protocol
,使用字符串访问一个对象实例变量的机制。而不是通过setter
、getter
方法等显式的存取方式去访问。例如,在动画中,可以使用KVC
的方式改变view的参数。
KVO
是一种机制,当制定的对象属性被修改后,对象就会接受到通知。
KVO
与notification
的不同
两者都是一对多,但是对象之间直接的交互,notification
明显得多,需要一个中间交互NSNotificationCenter
。