objc_msgsend消息快速查找(上)

211 阅读4分钟

引入

上一篇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方式,isKindOfClass
  • Runtime API方式,class_getInstanceSize

1.png

objc_msgSend汇编探究

LGPerson *person = [LGPerson alloc];
LGTeacher *teach = [LGTeacher alloc];
[person sayNB];

2.jpg

3.jpg

汇编显示objc_msgSendlibobjc.A.dylib系统库,实际上看objc_msgSend前缀是objc猜测应该在 objc源码中。在objc源码中全局搜索objc_msgSend,找到真机的汇编objc-msg-arm64.s

4.png

汇编中x0x1比较熟悉知道是寄存器。p0-p17就是对x0-x17重新定义

objc_msgSend汇编初探

5.png

  • receiver 和0 对比是否等于0 等于直接走 LReturnZero 将各个寄存器置空为0,结束这次消息发送 不等于0 判断是否支持
  • 支持Taggedpointer小对象类型,小对象为空 ,返回nil,不为nil处理isa获取class
  • 不支持Taggedpointer小对象类型 取[x0]receiver的地址也就是isa存到 p13 = isa走函数GetClassFromIsa_p16 p13, 1, x0

GetClassFromIsa_p16获取类

6.png

  • 走函数ExtractISA p16, \src, \auth_address

ExtractISA p16

7.png

  • 通过isa与掩码获取class继续走下面流程CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

CacheLookup

1.png

  • 获取的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

3.png

  • #define PTRSHIFT 3
  • 根据下标的index 计算出对应的bucket,#-BUCKET_SIZE bucket向前平移一个位置
  • 取出bucket中的sel,imp 与 传进来的p1=sel对比,如果相等,走流程2,缓存命中,走流程CacheHit如果不相等走流程3
  • 判断取出来的sel是否等于0,如果等于0 跳转 MissLabelDynamic 否则对比 查询的bucket的地址和首地址对比,如果大于 继续循环。
  • 如果找到最前面的第一个还是没有找到,继续走下面流程

CacheHit

5.png

.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
  • 缓存查询到以后直接对bucketimp进行解码操作。即imp = imp ^ class,然后调用解码后的imp
  • 返回 imp ,buckets ,sel ,isa.

缓存遍历图解

1.png

  • 如果既没有hash冲突又没有目标方法的缓存,那么hash下标对应的bucket就是空的直接跳出缓存查找
  • 不会出现中间是有空的bucket,两边有目标bucket这种情况

向前遍历缓存

2.png

  • p13 = buckets + (mask << 1+3) 找到最后一个bucket的位置

  • 先获取对应的bucket然后取出impsel存放到p17p9,然后*bucket--向前移动一个位置

  • p9= sel和 传入的参数_cmd进行比较。如果相等走2流程

  • 如果不相等在判断(sel != 0 && bucket > 第一次确定的hash下标bucket)接着循环缓存查找,如果整个流程循环完仍然没有查询到或者遇到空的bucket。说明该缓存中没有缓存),缓存查询结束跳转__objc_msgSend_uncached流程

缓存查找流程图

1.png