iOS深入理解KVC、KVO

243 阅读3分钟

一、KVC

KVC的全称是Key-Value Coding,俗称“键值编码”,KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。

经常用到的api

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 
+(BOOL)accessInstanceVariablesDirectly;// 此方法默认实现返回YES

KVC原理

+(BOOL)accessInstanceVariablesDirectly 默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

KVC赋值原理

图片.png

  1. set<Key>:_set<Key>的顺序查找对应命名的setter方法,如果找到的话,调用这个方法并将值传进去(根据需要进行对象转换);
  2. 如果没有发现setter方法,但是accessInstanceVariablesDirectly类属性返回YES,则按_<key>_is<Key><key>is<Key>的顺序查找一个对应的实例变量。如果发现则将value赋值给实例变量;
  3. 如果没有发现setter方法或实例变量,则调用setValue:forUndefinedKey:方法,默认抛出一个异常,但是一个NSObject的子类可以提出合适的行为。

KVC取值原理

图片.png

  1. 通过getter方法查找实例,以get<Key>, <key>, is<Key>, _<key>的顺序搜索符合规则的方法,如果有,就调用对应的方法;

  2. 如果没有找到getter方法,并且在类方法accessInstanceVariablesDirectly是返回YES的的情况下搜索一个名为_<key>_is<Key><key>is<Key>的实例;

  3. 如果返回值是一个对象指针,则直接返回这个结果;如果返回值是一个基础数据类型,但是这个基础数据类型是被NSNumber支持的,则存储为NSNumber并返回;如果返回值是一个不支持NSNumber的基础数据类型,则通过NSValue进行存储并返回;

  4. 在上述情况都失败的情况下调用valueForUndefinedKey:方法,默认抛出异常,但是子类可以重写此方法。

二、KVO

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

KVO原理

  • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数: willChangeValueForKey: 父类原来的setter didChangeValueForKey: 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

手动触发KVO

手动调用

- (void)didChangeValueForKey:(NSString *)key

直接修改成员变量会触发KVO么?

不会触发KVO

通过KVC修改属性会触发KVO么?

会触发KVO

如何手动关闭KVO

被观察的对象复写如下方法 返回NO即可关闭KVO

+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    return  NO;
}

如果关闭后还想触发 KVO的话 修改需要手动调用在变量setter的前后 主动调用 willChangeValueForKey:didChangeValueForKey:

如何对 NSMutableArray 进行 KVO

  • 一般情况下只有通过调用 set 方法对值进行改变才会触发 KVO。但是在调用NSMutableArray的 addObject或removeObject 系列方法时,并不会触发它的 set 方法。所以为了实现NSMutableArray的 KVO,官方为我们提供了如下方法:
 @property (nonatomic, strong) NSMutableArray *arr; 
//添加元素操作 
[[self mutableArrayValueForKey:@"arr"] addObject:item]; 
//移除元素操作
[[self mutableArrayValueForKey:@"arr"] removeObjectAtIndex:0];

KVO移除之后中间类的说明

  • 实例对象在注册KVO观察者之后,isa指针由原有类更改为指向中间类

  • 中间类重写了观察属性的setter方法、class、dealloc、_isKVOA方法

  • dealloc方法中,移除KVO观察者之后,实例对象isa指向由中间类更改为原有类

  • 中间类从创建后,就一直存在内存中,不会被销毁(考虑到重用问题(KVO再次注册),中间一旦注册到内存中去,就不会销毁)