Runtime (一)

前阵子因为学校有些事情没更新,今天开始会继续更新啦。

这个章节会讲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的

那么这个中间类又到底做了些什么来实现监听效果呢?

主要做了三件事情

  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也就不起作用了

2. KVC

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

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

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

  1. KVC的设值setValue: forKey:
    • 先查找set方法,如果有,直接调用;如果没有,下一步
    • 查看accessInstanceVariablesDirectly方法的返回值,看看能不能直接访问成员变量
      • 如果返回YES并且成员变量存在,直接访问成员变量并赋新值;如果成员变量不存在,下一步
      • 如果返回NO,下一步
    • 调用setValue: forUndefinedKey:并抛出异常
  2. KVC的取值valueForKey:
    • 先查找get方法,如果有,直接调用;如果没有,下一步
    • 查看accessInstanceVariablesDirectly方法的返回值,看看能不能直接访问成员变量
      • 如果返回YES并且成员变量存在,直接访问成员变量并取值;如果成员变量不存在,下一步
      • 如果返回NO,下一步
    • 调用valueForUndefinedKey:并抛出异常

还有一个KVC常见问题,KVC是否会触发KVO?**会!**记下来就好!

好啦,KVO/KVC介绍到这里,下期讲分类相关知识,欢迎继续关注 😃

我的牛客网账号是917470656,上面有我记录的几篇面经。

个人公众号:iOS开发学习

未经作者允许,禁止转载!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值