KVC
1.简介
KVC全称是Key Value Coding(键值编码),是可以通过对象属性名称(Key)直接给属性值(value)编码(coding)“编码”可以理解为“赋值”。这样可以免去我们调用getter和setter方法,从而简化我们的代码,也可以用来修改系统控件内部属性。
所谓键值编码,并不是访问器方法的启动和实例变量的访问这种直接的方式,而是使用表示属性的字符串来间接访问对象属性值的一种结构。
KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。
在NSKeyValueCoding中提供了KVC通用的访问方法,分别是getter方法valueForKey:和setter方法setValue:forKey:,以及其衍生的keyPath方法,这两个方法各个类通用的。并且由KVC提供默认的实现,我们也可以自己重写对应的方法来改变实现。
2.实现原理
KVC的定义都是对NSObject的扩展来实现的,Objective-C中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的,因为没有继承NSObject),下面是KVC最为重要的四个方法:
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
-
对于setValue: forKey:@"name";代码,执行机制如下:
程序优先调用“setName:属性值;”代码通过setter方法完成设置。
如果该类没有setName:方法,KVC机制会搜索该类名为_name的成员变量,找到后对_name成员变量赋值。
如果该类既没有setName:方法,也没有定义_name成员变量,KVC机制会搜索该类名为name的成员变量,找到后对name成员变量赋值。
如果上面3条都没有找到,系统将会执行该对象的 setValue: forUndefinedKey: 方法。默认setValue: forUndefinedKey:方法会引发一个异常,将会导致程序崩溃。
-
对于“valueForKey:@"name";”代码,执行机制如下:
程序优先调用"name;"代码来获取该getter方法的返回值。
如果该类没有name方法,KVC机制会搜索该类名为_name的成员变量,找到后返回_name成员变量的值。
.如果该类既没有name方法,也没有定义_name成员变量,KVC机制会搜索该类名为name的成员变量,找到后返回name成员变量的值。
如果上面3条都没有找到,系统将会执行该对象的valueForUndefinedKey:方法。默认valueForUndefinedKey:方法会引发一个异常,将会导致程序崩溃。
3.KVC补充
1. 处理不存在的key
我们可以考虑重写setValue: forUndefinedKey:方法与valueForUndefinedKey:方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"您设置的key:[%@]不存在", key);
NSLog(@"您设置的value为:[%@]", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"您访问的key:[%@]不存在", key);
return nil;
}
2. 处理nil值
当程序尝试为某个属性设置nil值时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。我们同样可以重写这个方法:
- (void)setNilValueForKey:(NSString *)key {
//对不能接受nil的属性进行处理
if ([key isEqualToString:@"price"]) {
//对应你具体的业务来处理
price = 0;
}else {
[super setNilValueForKey:key];
}
}
3. Key路径(Key Path)
KVC 同样允许我们通过关系来访问对象。假设 person 对象有属性 address,address 有属性 city,我们可以这样通过 person 来访问 city:
[person valueForKeyPath:@"address.city"];
这里我们调用 -valueForKeyPath: 而不是 -valueForKey:。
- setValue:forKeyPath: 根据Key路径设置属性值
- valueForKeyPath: 根据Key路径获取属性值
KVO
1.简介
KVO,即:Key-Value Observing,是 Objective-C 对 观察者模式(Observer Pattern)的实现。它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
2.实现原理
KVO是基于runtime机制实现的
当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制
如果原类为Person,那么生成的派生类名为** NSKVONotifying_Person **
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
键值观察通知依赖于NSObject的两个方法: willChangeValueForKey:和didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangevlueForKey:会被调用,继而observeValueForKey:ofObject:change:context: 也会被调用。
补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
苹果官方文档内容
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
大致意思为:
苹果使用了一种isa交换的技术,当ObjectA的被观察后,ObjectA对象的isa指针被指向了一个新建的子类NSKVONotifying_ObjectA,且这个子类重写了被观察值的setter方法和class方法,dealloc和isKVO方法,然后使ObjectA对象的isa指针指向这个新建的类,然后事实上ObjectA变为了NSKVONotifying ObjectA的实例对象,执行方法要从这个类的方法列表里找。
3.基本使用
注册观察者,实施监听
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
context:nil];
在这个回调方法里处理属性的变化
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
//...实现监听处理
}
移除观察者
[self removeObserver:self forKeyPath:@"age"];
1.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用 willChangeValueForKey 方法、原来的 setter 方法实现、didChangeValueForKey 方法,而 didChangeValueForKey 方法内部又会调用监听器的 observeValueForKeyPath:ofObject:change:context: 监听方法。
2.如何手动触发KVO
答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用 willChangeValueForKey 和 didChangeValueForKey 方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。
参考文章:
https://juejin.im/post/5ac5f4b46fb9a028d5675645