前阵子因为学校有些事情没更新,今天开始会继续更新啦。
这个章节会讲Runtime相关知识,这期先聊一聊面试常考的KVO/KVC。
1. KVO
KVO全称是Key-Value Observing,意思就是给一个key添加一个监听者observer,如果这个key的value值发生了改变,就会触发这个监听者的相关方法,监听者就得到通知了并且可以做出相应动作。
面试中常考的问题是KVO的原理。
其实原理很简单,就是Runtime会生成一个被监听对象所属的类的子类,并且使被监听对象的isa指针指向这个子类。
啥意思呢?
// 假设我们有一个Person类,它有一个age属性
Person *myPerson = [Person new];// 给它添加一个监听
[myPerson addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];// 那么对于Runtime,其实会生成一个中间类,我们称作MiddlePerson,这个类是原本的Person类的子类
// 而myPerson对象,从代码上看,其isa指针似乎是指向Person类
// 但是在底层,其isa指针其实是指向MiddlePerson的
那么这个中间类又到底做了些什么来实现监听效果呢?
主要做了三件事情:
- 重写被监听属性的set方法。例如在上面的例子中,就会重写
MiddlePerson
类中age
属性的set方法。
在重写后的set方法中,干了下面几件事情:- 先调用willChangeValueForKey方法
- 再调用父类的set方法,也就是原本的set方法(在上面的例子中就是
Person
类的set
方法),来修改属性值 - 最后调用didChangeValueForKey方法,在这个方法中,会调用监听对象的observeValueForKey方法,告知监听对象属性值已经发生改变
- 重写中间类的class方法,使其返回父类,也就是原本的类的class对象。
做这一步主要是为了隐藏KVO实现细节,使开发者感觉不到中间对象的存在。例如在上面的例子中,就会重写MiddelPerson
类的class方法,使其直接return [Person class]
。 - 生成一个新的方法_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也就不起作用了。
2. 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?**会!**记下来就好!
好啦,KVO/KVC介绍到这里,下期讲分类相关知识,欢迎继续关注 😃
我的牛客网账号是917470656,上面有我记录的几篇面经。
个人公众号:iOS开发学习
未经作者允许,禁止转载!