Runtime (一)

news/2024/12/12 6:22:04/

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

这个章节会讲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开发学习

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


http://www.ppmy.cn/news/626499.html

相关文章

Run Run Run

学习了 2022 年集训队论文 《浅谈与 Lyndon 理论有关的字符串组合问题》 写得很好,像我这样的字符串小白也能看懂 Lyndon 分解 若字符串 w w w 小于它的每一个真后缀,则称 w w w 是 Lyndon 串。若字符串 w w w 是 Lyndon 串,则 w k w ′…

Runc 与 Cgroups

Runc 可以算是启动创建容器的最后一步,其中设置 Cgroups,隔离 namespaces,配置网络,挂载相应的卷 等一系列操作 本文将主要讲 runc 是如何去操作系统中的 Cgroups,实现对资源的限制和管理的 Runc 支持三种方式来限制管…

一个简易版的T4代码生成框架

对于企业开发来说,代码生成在某种意义上可以极大地提高开发效率和质量。在众多代码生成方案来说,T4是一个不错的选择,今天花了点时间写了一个简易版本的T4代码生成的“框架”,该框架仅仅是定义了一些基本的基类以及其他与VS集成相…

单片机esp32s2实现win10之USB副屏

视频演示: 骚气双副屏,单片机实现win10 USB副屏演示esp32 s2_哔哩哔哩_bilibili-https://www.bilibili.com/video/BV1tU4y1F7B6?spm_id_from333.999.0.0 开源计划 2021年最后一天,庆祝新年。 github开源地址如下,欢迎复刻魔改…

Linux服务器Jenkins部署打包Flutter

程序猿日常 记Jenkins部署打包Flutter参考Linux服务器Jenkins部署打包Flutter 安装Flutter环境 Flutter SDK 下载地址 配置服务器Flutter环境变量 创建任务 #!/bin/bash -ilex source /etc/profileflutter clean flutter pub get flutter build apk

王爽《汇编语言》期末考试题库(附答案)

单选题 第一章 PC机的最小信息单位是( )。 A. bit B. 字节 C. 字长 D. 字 A PC机的最小信息单位是比特(bit),常用来表示一位二进制数字(0或1)。字节(byte)是计算机中常用的数据单位,一个字…

Java 微服务框架选型

微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每…

互联网快讯:喜茶回应上市;掌门教育、猿辅导转型素质教育

零售电商 1、淘宝逛逛推出双11种草期:10月1日起为达人设立亿级资源奖励 2、达达集团、京东与微软三方达成战略合作,共创数码3C产品1小时达零售新模式 3、喜茶、乐乐茶回应赴港上市:暂无上市计划 4、全家计划至2024财年开设1000家无人结…