KVO与KVC

KVO

 KVO全称为Key-Value Observing,意思就是给一个Key添加一个监听者observer,如果这个key的value值发生改变,就会触发监听者的相关方法,监听者得到通知并且做出对应动作。

 KVO的原理:Runtime会生成一个被监听对象所属的类的子类,并且使被监听对象的isa指针指向这个子类。

假设有一个Person类,有一个name属性
Person *myPerson =[Person new]
给它添加一个监听
[Person addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionNew context:nil];
对于Runtime,会生成一个中间类,称为MiddlePerson,是Person类的子类,对于myPerson对象,从代码上看,其isa指针指向Person类,但在底层,其实isa指针其实指向MiddlePerson

主要做了三件事情:

  1. 重写被监听属性的set方法。例如在上面的例子中,就会重写MiddlePerson类中age属性的set方法。 在重写后的set方法中,干了下面几件事情:先调用willChangeValueForKey方法再调用父类的set方法,也就是原本的set方法(在上面的例子中就是Person类的set方法),来修改属性值最后调用didChangeValueForKey方法,在这个方法中,会调用监听对象的observeValueForKey方法,告知监听对象属性值已经发生改变
  2. 重写中间类的class方法,使其返回父类,也就是原本的类的class对象。 做这一步主要是为了隐藏KVO实现细节,使开发者感觉不到中间对象的存在。例如在上面的例子中,就会重写MiddelPerson类的class方法,使其直接return [Person class]。
  3. 生成一个新的方法_isKVOA。这个方法直接返回YES,表明这个类是为了实现KVO而创建的,是一个中间类。

另外,还有一个常考的地方是,如何手动触发KVO?(意思就是如果不调用set方法,直接更改属性对应的变量值,如何触发KVO)

其实这个问题中本身就隐藏了一个考点,那就是只有通过调用属性的set方法才会触发KVO,直接更改属性值不会触发KVO。 例如在上面的例子中,只有我们通过myPerson.age=18这种方法,才会触发KVO。 但假如Person类本身有一个increaseAge方法,该方法实现中直接通过访问成员变量来实现,例如

void increaseAge {
    _age++;
}

那么在这种情况下,是不会触发KVO的。

如果想要触发,应该怎么做呢?

void increaseAge {
    // 1.先调用willChangeValueForKey方法,这里就写个伪代码
    willChangeValueForKey(@"age");
    
    // 2.再更改成员变量值
    _age++;
    
    // 3.最后调用didChangeValueForKey方法,这里就写个伪代码
    didChangeValueForKey(@"age");
}

可以看到,这里的实现其实就是把Runtime添加的中间类重写的set方法,自己写了一遍。

注意:这里的willChangeValueForKey和didChangeValueForKey缺一不可。例如,虽然observeValueForKey是在didChangeValueForKey方法中被调用起来的。所以看起来好像只要有了didChangeValueForKey方法,监听对象自然就会收到通知,那么willChangeValueForKey就没必要存在了。但其实不是,KVO底层应该有某些判断,如果willChangeValueForKey没有被执行,那么didChangeValueForKey也就不起作用了。

KVC

KVC全称是Key-Value Coding,意思是直接通过键值对的形式,来访问某个成员变量值,而不是单纯通过属性的set方法。

这有啥用?例如有些类,它可能有一些私有的成员变量或属性,并不公开出来。这时候如果想通过set方法访问,是无法访问的(无法通过编译);但如果通过KVC机制,你就可以轻松办到,因为Runtime在编译时根本不会关心这个属性是否公开,甚至不会关心这个成员变量是否存在(即使不存在,最后报个error就好了)。

KVC的过程是常考的点,其实很简单,把下面的理解记忆就好了。

KVC的设值setValue: forKey:先查找set方法,如果有,直接调用;如果没有,下一步查看accessInstanceVariablesDirectly方法的返回值,看看能不能直接访问成员变量如果返回YES并且成员变量存在,直接访问成员变量并赋新值;如果成员变量不存在,下一步如果返回NO,下一步调用setValue: forUndefinedKey:并抛出异常
KVC的取值valueForKey:先查找get方法,如果有,直接调用;如果没有,下一步查看accessInstanceVariablesDirectly方法的返回值,看看能不能直接访问成员变量如果返回YES并且成员变量存在,直接访问成员变量并取值;如果成员变量不存在,下一步如果返回NO,下一步调用valueForUndefinedKey:并抛出异常
还有一个KVC常见问题,KVC是否会触发KVO?会!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值