** KVC:
* 1.字典转模型 ,简化代码量
* 2.修改系统的只读变量: 例如自定义tabBar的时候,由于tabBar是只读属性,只能用KVC赋值.
* 3.可以任意修改一个对象的属性和变量\(包括私有变量\)
* 4.可以通过运算符层次查找对象的属性,setValue:forKeyPath:
假如person这个类中我们又有个属性dog,Dog类中又有个属性名字那么我们怎么通过'p'这个对象去设置狗的属性呢?
// 初始化Dog对象
p.dog= [[Dog alloc] init];
// 给dog对象赋值
[p setValue:@"旺财" forKey:@"dog.name"];
结果:
逐级寻找key错误演示.png
如果我们使用 setValue:forKey: 这个方法,Xcode会报错说找不到dog.name这个key,想想我们在stroyboard中,如果我们控件连线出现错误,也会报相似的错误,说明了stroyboard在赋值的时候也是通过kvc的方式来操作的
现在我们来试试用 setValue:forKeyPath: 方法
[p setValue:@"旺财"forKeyPath:@"dog.name"];
结果:
逐级寻找key演示.png
成功了,说明 setValue:forKeyPath: 方法中包含了 setValue:forKeyPath:的方法,但是内部增加了更高级的功能 —— 内部实现:它会先去 person类中寻找有没有 dog这个key,如果有,那么会去Dog类中寻找有没有name这个key,如果有,就给name这个key赋值
KVC的缺点:
一旦使用 KVC 编译器将无法检查出错误,即不会对设置的键、键路径进行错误检查,且执行效率要低于自定的 setter 和getter 方法。因为使用 KVC 键值编码,它必须先解析字符串,然后在设置或者访问对象的实例变量。
* * KVO:
在 Objective-C 中如何实现 KVO
- 注册观察者(注意:观察者和被观察者不会被保留也不会被释放)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context;
- 接收变更通知
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- 移除对象的观察者身份
- (void)removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath; - KVO中谁要监听谁注册,然后对响应进行处理,使得观察者与被观察者完全解耦。KVO只检测类中的属性,并且属性名都是通过NSString来查找,编译器不会检错和补全,全部取决于自己。
KVO的底层实现:
KVO是基于runtime机制实现的/small-talk
- 当某个类的属性对象第一次被观察时,系统就会在运行期间动态地创建该类的一个子类,在这个子类中重写父类的任何被观察属性的setter方法。子类在被重写的setter方法内实现真正的通知机制
- 如果原类为Person,那么生成的子类名为NSKVONotifying_Person
- 我们知道,每一个类中都有一个isa指针指向当前类,所有系统就是在当一个类的对象第一次被观察的时候,系统就会偷偷将isa指针指向动态生成的子类,从而在被监听属性赋值时被执行的是子类的setter方法
- 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
- 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的子类:如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。
(isa 指针的作用:每个对象都有isa指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)
总结:
KVC/KVO实现的根本是Objective-C的动态性和runtime
对比其他的回调方式,KVO机制的运用的实现,更多的由系统支持,相比notification、delegate等更简洁些,并且能够提供观察属性的最新值以及原始值;但是相应的在创建子类、重写方法等等方面的内存消耗是很巨大的。所以对于两个类之间的通信,我们可以根据实际开发的环境采用不同的方法,使得开发的项目更加简洁实用。
另外需要注意的是,由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么KVO不会有任何开销,因为此时根本就没有KVO代码存在。但是即使没有观察者,委托和NSNotification还是得工作,这也是KVO此处零开销观察的优势。