iOS面试--Runtime(动态运行时)
前言
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 操作
- 弱引用相关
- 关联对象相关方法
-
内存管理相关方法
id 内部结构体
Class == objc_class
objc_class- isa 指针
-
共用体 isa_t
isa 指针其实在 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
主要用于提高方法调用的速度或者消息传递的速度
- 用于方法执行函数
- 是一个的哈希表结构 (用哈希主要是提高查找效率)
- 是的最佳应用
局部性原理:把调用方法频率最高的放到缓存中以提高命中率
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 的数据结构class_rw_t 原理解析
https://www.jianshu.com/p/585845127007
class_ro_t
class_ro_t 数据结构method_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)。