KVC , KVO
1. KVC键值编码
KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
由于OC的语言特性,根本不必进行任何操作就可以进行属性的动态读写,这种方式就是Key Value Coding(简称KVC)。
KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说ObjC中几乎所有的对象都支持KVC操作,常用的KVC操作方法
动态设置 :
setValue:属性值 forKey:key(属性名)(用于简单路径)
setValue:属性值 forKeyPath:key(属性路径) (用于复合路径,例如Person有一个Account类型的属性,那么person.account就是一个复合属性)
动态获取 :
valueForKey:属性名
valueForKeyPath:属性名(用于复合路径)
注意
key的值必须正确,如果拼写错误,会出现异常
当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来
因为类key反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去
例子
一个对象拥有某些属性。比如说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另一个是获取 key 的值。如下面的例子:
void changeName(Person *p, NSString *newName) {
// using the KVC accessor(getter)method
NSString *originalName=[p valueForKey:@"name"];
// using the KVC accessor(setter)method.
[p setValue:newName forKey:@"name"];
NSLog(@"Changed %@'s name to: %@", originalName, newName);
}
现在,如果 Person 有另外一个 key 配偶(spouse),spouse 的 key 值是另一个 Person 对象,用 KVC 可以这样写:
void logMarriage(Person *p){
// just using the accessor again, same as example above
NSString *personsName=[p valueForKey:@"name"];
// this line is different, because it is using
// a"key path"instead of a normal"key"
NSString *spousesName=[p valueForKeyPath:@"spouse.name"];
NSLog(@"%@ is happily married to %@", personsName, spousesName);
}
key 与 key pat 要区分开来,key 可以从一个对象中获取值,而 key path 可以将多个 key 用点号 "." 分割连接起来,比如:
[p valueForKeyPath:@"spouse.name"];
相当于这样……
[[p valueForKey:@"spouse"]valueForKey:@"name"];
KVC底层实现 :
当一个对象调用setValue方法时,方法内部会做以下操作 :
1>检查是否存在相应key的set方法,如果存在就调用set方法
2>如果set方法不存在,就会查找与key相同名称并且带下划线的的成员变量,如果有,则直接赋值
3>如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值
4>如果还没有找到,则调用valueForUnderfindKey:和setValue: forUnderfindKey: 方法
这些方法的实现默认都是抛出异常,我们可以根据需要重写他们。
2. KVO键值监听
KVO全称Key-Value Observing。
典型的观察者模式承载者。
基于监控键值发生变化,通知观察者。
KVO 就是基于 KVC 实现的关键技术之一。
KVO其实是一种观察者模式,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所有的OC对象都可以使用KVO。
在ObjC中使用KVO操作常用的方法如下:
注册指定Key路径的监听器: addObserver: forKeyPath: options: context:
删除指定Key路径的监听器: removeObserver: forKeyPath、removeObserver: forKeyPath: context:
回调监听: observeValueForKeyPath: ofObject: change: context:
KVO的使用步骤也比较简单:
通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器
重写监听器的observeValueForKeyPath: ofObject: change: context:方法。
KVO的底层实现 :
1>KVO是基于runtime机制实现
2>使用了isa混写(isa-swizzling),当一个对象(假设是person对象,person的类是MYPerson)的属性值(假设person的age)发生改变时,系统会自动生成一个类,继承自MYPerson : NSKVONotifying_MYPerson,在这个类的setAge方法里面,调用[super setAge : age] [self willChangeValueForKey:@"age"] 和 [self didChangeValueForKey:@"age"] ,而这两个方法内部会主动调用监听者内部的 -(void)observeValueForKeyPath 这个方法。
3>想要看到 NSKVONotifying_MYPerson很简答,在 self.person.age = 20;这里打断点,在调试区域就能看到 _person->NSObject->isa=(Class)NSKVONotifying_MYPerson. 同时我们在 self.person = [[MYPerson alloc]init]; 后面打断点,看到 _person->NSObject- >isa=(Class)MYPerson, 由此可见,在添加监听者之后,person 类型已经由 MYPerson 被改变为 NSKVONotifying_MYPerson