前言
2020年已经注定了:新型冠状病毒肆虐,追了 15 年的偶像Kobe与世长辞,禽流感来袭等等。这是多灾多难的一年! 太多的坏事情坏消息消耗了人们太多的热情和信心,我们只能将一切寄予希望,即使是很空虚的希望;我们憧憬新型冠状病毒的离开,生活重归于好,我们期待时间能够治愈科比离世带给我们的痛苦,所有的这些坏事情坏消息仅仅是一个开始,它们还没有开足马力,由于惯性,还会横冲直撞一段时间和路程。 过苦日子的悲惨现实,已经支撑不起伟大、强大和辉煌的梦想。所以我们只能寄希望于未来,而为了我们的未来,所以还是要继续前行!!!
Dear thoughts are in my mind, and my soul it soars enchanted.
矫情之后,还是要回归正题。关于 Runtime 的面试,我们主要从下面几个问题出发。
- 编译时语言和 Objective-C 这种动态运行时语言有何区别?
- 消息传递和函数调用有什么不同?
- 当我们调用一个方法没有实现时,系统是如何为我们转发?
- 当我们在消息传递的过程中,如何进行方法缓存的查找?
Runtime
runtime 概述
runtime 我们主要是从以下八个模块去分析,数据结构、类对象与元类对象、消息传递、方法缓存、方法转发、Method-Swizzling、动态添加方法以及动态方法解析,如下图所示
runtime 数据结构
objc_object
objc_object 其实就是 id,每一个 objc_object 中均有一个 isa 指针
objc_object 数据结构
- isa_t 指针
- 关于 isa 操作
- 弱引用相关
- 关联对象相关方法
-
内存管理相关方法
Class == objc_class
- isa 指针
-
共用体 isa_t
其实在 32 位架构的机器上,那么 isa 指针的位数就是 32 位,64 位机器则为 64 位。如图所示以 64 位为例,isa指针包括指针型 isa 指针和非指针型 isa 指针,指针型的 isa 的值代表的就是 class 的地址,对于非指针型的 isa,那么 isa 的部分值代表 class 的地址,为什么会这样设计,因为有时候 class 的地址的长度不需要那么长,那么多余的位数可以用来存储其他的值。
isa 指向
- 关于对象的 isa 指针,其指向的是类对象
- 对于实例对象来说(id),isa 指针指向的是其 Class 对象;
- 关于类对象(Class),其 isa 指针指向的是其元类对象(meta_class)
cache_t
主要用于提高方法调用的速度或者消息传递的速度
- 用于方法执行函数
- 是一个的哈希表结构 (用哈希主要是提高查找效率)
- 是的最佳应用
局部性原理:把调用方法频率最高的放到缓存中以提高命中率
class_data_bits_t
- class_data_bits_t 主要是对 class_rw_t 的封装
- class_rw_t 代表的是类相关的读写信息、对 class_ro_t 的封装
- class_ro_t 代表的是类的只读信息
class_rw_t
class_rw_t 原理解析
https://www.jianshu.com/p/585845127007
class_ro_t
method_t
函数四要素:函数的名称、函数的返回值、函数的参数、函数体
如上图所示
- name:方法的名称
- types:函数的返回值和参数的组合
- imp: 无类型的函数指针,指向的就是函数体
对象、类对象、元类对象
- 类对象存储实例方法列表等信息
- 元类对象存储类对象的方法列表等信息
如上图所示,实例对象可以通过 isa 指针找到其类对象,而类对象可以通过 isa 指针找到他的元类对象;同时类对象可以通过 superclass 指针找到它的 Superclass 对象一直到 Rootclass,元类对象也是如此;类对象和元类对象因为继承自 NSObject, 所以他们中也有 isa 指针,对于每一个元类对象的 isa 指针都指向根元类对象;根元类对象的 superclass指针指向的是根类对象
消息传递
缓存查找
类对象查找(已排序的列表采用二分查找、未排序的使用一般遍历)
元类对象查找(父类逐级遍历)
消息转发流程
https://www.jianshu.com/p/ba0e9942a082
Method Swizzling
动态添加一个方法?
当面试官问你@dynamic关键字的时候,如何回答?
- 动态运行时语言将函数的决议推迟到运行时
- 编译时语言在编译期间进行函数的决议
runtime 实际面试问题
1. [obj foo] 和 objc_msgsend()函数之间有什么关系?
objc_msgSend()是[obj foo]的具体实现。
在runtime中,objc_msgSend()是一个c函数,[obj foo]会被翻译成这样的形式objc_msgSend(obj, foo)。
系统会先去obj的对应的类中找方法;
再进行缓存,找不到再去找方法列表;
再找父类,如此逐级向上传递;
最后再找不到就要转发。
2. runtime 如何通过Selector 找到对应的 IMP 地址的?(考察消息传递机制)
对于实例方法,每个实例的 isa 指针指向着对应类对象,
而每一个类对象中都一个对象方法列表。
对于类方法,每个类对象的 isa 指针都指向着对应的元对象,
而每一个元对象中都有一个类方法列表。
方法列表中记录着方法的名称,方法实现,以及参数类型,
其实 selector 本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。
Selector、Method 和 IMP 的关系可以这样描述:在运行时分发消息,方法列表中的每一个实体都是一个方法(Method),
它的名字叫做选择器(SEL),对应着一种方法实现(IMP)。