引入
上一篇cache_t结构以及缓存分析到当对象调用方法时,会调用cache的insert方法去缓存起来imp和sel,以及分析到了insert之前会调用objc_msgSend --> _objc_msgSend_uncached --> lookUpImpOrForward --> log_and_fill_cache --> cache_t::insert,本来将继续探究方法写入过程。
准备工作
Objective-C Runtime Introduction
Objective-C 语言将尽可能多的决策从编译时和链接时推迟到运行时。只要有可能,它就会动态地做事。这意味着该语言不仅需要编译器,还需要运行时系统来执行编译后的代码。运行时系统充当Objective-C语言的一种操作系统;这就是使语言起作用的原因。
Runtime Versions and Platforms
Objective-C 运行时有两个版本——“现代”和“传统”。 现代版本是在 Objective-C 2.0 中引入的,包括许多新功能。 Objective-C 1 Runtime Reference 中描述了旧版运行时的编程接口; Objective-C 运行时参考中描述了现代版运行时的编程接口。
最显着的新特性是现代运行时中的实例变量是“非脆弱的”:
在遗留运行时中,如果更改类中实例变量的布局,则必须重新编译从它继承的类。 在现代运行时中,如果更改类中实例变量的布局,则不必重新编译从它继承的类。 此外,现代运行时支持声明属性的实例变量综合。
编译时:顾名思义正在编译的时候,啥叫编译呢?就是编译器把源代码翻译成机器能够识别的代码。编译时会进行词法分析,语法分析主要是检查代码是否符合苹果的规范,这个检查的过程通常叫做静态类型检查运行时:代码跑起来,被装装载到内存中。运行时检查错误和编译时检查错误不一样,不是简单的代码扫描,而是在内存中做操作和判断
Runtime调用三种方式
Objective-C方式,[penson sayHello]Framework & Serivce方式,isKindOfClassRuntime API方式,class_getInstanceSize
objc_msgSend汇编探究
LGPerson *person = [LGPerson alloc];
LGTeacher *teach = [LGTeacher alloc];
[person sayNB];
汇编显示objc_msgSend在libobjc.A.dylib系统库,实际上看objc_msgSend前缀是objc猜测应该在 objc源码中。在objc源码中全局搜索objc_msgSend,找到真机的汇编objc-msg-arm64.s
汇编中x0,x1比较熟悉知道是寄存器。p0-p17就是对x0-x17重新定义
objc_msgSend汇编初探
receiver 和0对比是否等于0 等于直接走LReturnZero将各个寄存器置空为0,结束这次消息发送 不等于0 判断是否支持- 支持
Taggedpointer小对象类型,小对象为空 ,返回nil,不为nil处理isa获取class - 不支持
Taggedpointer小对象类型 取[x0]receiver的地址也就是isa存到p13 = isa走函数GetClassFromIsa_p16 p13, 1, x0
GetClassFromIsa_p16获取类
- 走函数
ExtractISA p16, \src, \auth_address
ExtractISA p16
- 通过
isa与掩码获取class继续走下面流程CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
CacheLookup
-
获取的
class p16平移2*8字节大小得到cache赋值给p11 = 结构体首地址_bucketsAndMaybeMask 也就是buckets的地址p11 &掩码赋值给p10 = buckets -
p1= sel eor是异或 p12 = sel ^ (sel>>7)cache_hash计算下标index计算源码p12=value value ^= value >> 7; -
p12 = p12 & p11>>48 p11 >>48 = cache >>48取高16位 =_maybeMask也就是mask p12 = value &mask = index -
目的就的为了计算查找的开始位置
index
#define PTRSHIFT 3- 根据下标的
index计算出对应的bucket,#-BUCKET_SIZEbucket向前平移一个位置 - 取出bucket中的
sel,imp与 传进来的p1=sel对比,如果相等,走流程2,缓存命中,走流程CacheHit如果不相等走流程3 - 判断取出来的
sel是否等于0,如果等于0 跳转MissLabelDynamic否则对比 查询的bucket的地址和首地址对比,如果大于 继续循环。 - 如果找到最前面的第一个还是没有找到,继续走下面流程
CacheHit
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
- 缓存查询到以后直接对
bucket的imp进行解码操作。即imp=imp^class,然后调用解码后的imp - 返回
imp ,buckets ,sel ,isa.
缓存遍历图解
- 如果既没有
hash冲突又没有目标方法的缓存,那么hash下标对应的bucket就是空的直接跳出缓存查找 - 不会出现中间是有空的
bucket,两边有目标bucket这种情况
向前遍历缓存
-
p13=buckets+(mask << 1+3)找到最后一个bucket的位置 -
先获取对应的
bucket然后取出imp和sel存放到p17和p9,然后*bucket--向前移动一个位置 -
p9=sel和 传入的参数_cmd进行比较。如果相等走2流程 -
如果不相等在判断(
sel!=0&&bucket> 第一次确定的hash下标bucket)接着循环缓存查找,如果整个流程循环完仍然没有查询到或者遇到空的bucket。说明该缓存中没有缓存),缓存查询结束跳转__objc_msgSend_uncached流程