从KVC说起
要说KVO
害的先说下KVC
,KVC(Key-value coding)
是一种基于NSKeyValueCoding
非正式协议的机制,能让我们直接使用一个或一串字符串标识符去访问,操作类的属性。
常用的方法比如:
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
通过这些方法加上正确的标识符(一般和属性同名),可以直接获取或者设置一个类的属性,甚至可以轻易越过多个类的层级结构,直接获取目标属性。
注册KVO
添加一个观察者的方法是-addObserver:forKeyPath:options:context:
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
observer:
注册KVO通知的对象。观察者必须实现key-value observing
方法observeValueForKeyPath:ofObject:change:context:
。
-
keyPath:
观察者的属性的keyPath
,相对于接受者,值不能为nil
。 -
options:``NSKeyValueObservingOptions
的组合,它指定了观察通知中包含了什么,可以查看“NSKeyValueObservingOptions”
。 -
context:
在observeValueForKeyPath:ofObject:change:context:
传给observer
参数的随机数据
KVO基本原理:
-
KVO
是基于runtime
机制实现的 -
当某个类的属性对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的
setter
方法。派生类在被重写的setter
方法内实现真正的通知机制。 -
如果原类为
Person
,那么生成的派生类名为NSKVONotifying_Person
-
每个类对象中都有一个
isa
指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa
指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter
方法 -
键值观察通知依赖于
NSObject
的两个方法:willChangeValueForKey:
和didChangeValueForKey:
,在一个被观察属性发生改变之前,willChangeValueForKey:
一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:
会被调用,继而observeValueForKey:ofObject:change:context:
也会被调用。
KVO深入原理:
-
Apple使用了
isa
混写(isa-swizzling)来实现KVO
。当观察对象为类A
的属性时,KVO
机制动态创建一个新的名为:NSKVONotifying_A
的新类,该类继承自A
的本类,且KVO
为NSKVONotifying_A
重写观察属性的setter
方法,setter
方法会负责在调用原setter方法之前和之后,通知所有观察对象属性值的更改情况。 -
NSKVONotifying_A
类剖析:在这个过程,被观察对象的isa
指针从指向原来的A
类,被KVO
机制修改为指向系统新创建的子类NSKVONotifying_A
类,来实现当前类属性值改变的监听。 -
所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对
KVO
的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”
的类,就会发现系统运行到注册KVO
的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A
的中间类,并指向这个中间类了。 -
isa
指针的作用:每个对象都有isa
指针,指向该对象的类,它告诉runtime
系统这个对象的类时什么。所以对象注册为观察者时,isa
指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。因而在该对象上对setter
的调用就会调用已重写的setter
,从而激活键值通知机制。 -
子类
setter
方法剖析:KVO
的键值观察通知依赖于NSObject
的两个方法:willChangeValueForKey:
和didChangeValueForKey:
,在存取数值的前后分别调用2个方法:被观察属性发生改变之前,willChangeValueForKey:
被调用,通知系统该keyPath
的属性值即将变更;在改变发生之后,didChangeValueForKey:
被调用,通知系统该keyPath
的属性值已经变更;之后,observeValueForKey:ofObject:change:context:
也会被调用。且重写观察属性的setter
方法这种继承方式的注入是在运行时而不是编译时实现的。

附加
1、什么是KVC,说说它的优缺点?
答:KVC
是一种不需要调用存取方法,就能直接通过实例变量访问对象属性的机制。很多情况下会简化程序代码。
但由于KVC
不会对键和建路径进行错误检查,所以编译器无法检测错误。而且使用KVC
后的执行效率要低于合成存取器,因为使用KVC
必须先解析字符串,然后再设置或服务对象的实例变量。
2、NSNotification和KVO的区别和用法是什么?什么时候用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?
答:NSNotification
是通知模式在iOS
的实现,KVO
的全称是简直观察(Key-value observing)
,其实基于KVC(Key-value coding)
的,KVC
是一个通过属性名访问属性变量的机制。将Model
层的变化,通知到多个Controller
对象时,可以使用NSNotification
;如果是只需要观察某个对象的某个属性,可以使用KVO
。
3、如何关闭默认的KVO的默认实现,KVO的实现原理?
答:所谓的“手动触发”是区别于“自动触发”:
自动触发是指类似这种场景:在注册KVO
之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。
想知道如何手动触发,必须知道自动触发KVO
的原理:
键值观察通知依赖于NSObject
的两个方法:willChangeValueForKey:
和didChangeValueForKey:
。在一个被观察属性发生改变之前,willChangeValueForKey:
一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:
会被调用,继而observeValueForKey:ofObject:change:context:
也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
网友评论