前言: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
前言
在上一篇,我们探索了 Runtime
以及与 Runtime
交互的三种方式,和总结了 方法的本质其实是消息发送的过程
。今天接着上一篇继续 objc_msgSend汇编分析
。
objc_msgSend汇编分析
cmp p0, #0
: p0为此次的消息接受者,拿来和0比较,判断消息接受者是否为0,如果没有消息接受者,则此次 objc_msgSend 没有意义。#if SUPPORT_TAGGED_POINTERS
判断是否为 SUPPORT_TAGGED_POINTERS 类型,如果是,则执行b.le LNilOrTagged
, 否则, 执行b.eq LReturnZero
,即返回此次消息为空。ldr p13, [x0]
将x0
存入到p13
,x0
是receiver
,即类,即类的首地址,即isa
,也就是说p13=isa
。进入
GetClassFromIsa_p16
带入参数src=p13
,needs_auth=1
,auth_address=x0
. 判断是不是 SUPPORT_INDEXED_ISA (32位isa),不满足此条件,接下来会进入__LP64__
(这份源码里指的是Mac OS X)分支。由于
_need_auth=1
,进入分支ExtractISA p16, \src, \auth_address
,此ExtractISA
为宏,操作是将\src(isa)、#ISA_MASK
做与操作,得到了Class
,结果存入到p16
中。LGetIsaDone
:获取isa完成。接下来执行CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
6.1 `mov x15, x16` 隐藏isa,将x16寄存器赋值到 x15
6.2 `ldr p11, [x16, #CACHE]` #define CACHE 8,那么p11=x16+0x8,等同于 isa+0x8, 即isa向右偏移了8字节,拿到了cache_t,即 p11=cache_t (我们探索真机环境也就是arm64的汇编,所以是CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16分支)
6.3 在 `CONFIG_USE_PREOPT_CACHES` 分支中,我们探索非A12及以后芯片,所以 不进入到 `#if __has_feature(ptrauth_calls)` 分支, 所以 执行 `and p10, p11, #0x0000fffffffffffe` 将p11与上#0x0000fffffffffffe (preoptBucketsMask) 得到 buckets() 的地址,存储在 p10中。 然后执行 `tbnz p11, #0, LLookupPreopt\Function` , 作用是验证p11, 也就是 cache_t 是不是为0, 如果为0, 则证明没有缓存, 没有向下继续查找 bucket的必要, 跳转至 `LLookupPreopt`。
6.4 `eor p12, p1, p1, LSR #7` , 因为 p0 寄存器是 receiver, p1 寄存器为第二个参数, SEL _cmd, 所以p1 = _cmd,对应上面的指令就是得出, p12 = (_cmd >> 7) ^ _cmd
6.5 `and p12, p12, p11, LSR #48` , p11 = cache_t = _bucketsAndMaybeMaske, 可以翻译成 p12 = p12 & (_bucketsAndMaybeMask >> 48), 这个指令最终的结果是找到已知buckets的index。
6.6 `add p13, p10, p12, LSL #(1+PTRSHIFT)` , PTRSHIFT 在 `__LP64__` 下的值为3,否则为2, 我们探索的64位的,所以, PTRSHIFT=3,p10 是 buckets, p12 是 index, 那么可以将上述指令翻译为 : p13 = p10 + (p12 << (1+3)), 将 index 左移4位, 然后将得到结果n, 在buckets的首地址上移动相应n个步长,找到最终的bucket_t。
6.7 `1: ldp p17, p9, [x13], #-BUCKET_SIZE`将 x13 寄存器的值取出来放在 p17和p9, 因为x13 为bucket_t结构体,在arm64架构下,第一个值是imp,第二个值是sel,所以p17=imp,p9=sel。
6.8 `cmp p9, p1` 比较p1和p9,即比较在缓存中取出来的sel和objec_msgSend的第二个参数——cmd,如果不等,向后跳转,执行命令 `b.ne 3f` ,将执行三条指令:
6.8.1 `cbz p9, \MissLabelDynamic` :找不到sel 所以 MissLabelDynamic
6.8.2 `cmp p13, p10` 循环查找的条件,当要查找的bucket_t的地址大于buckets的首地址的时候,继续查找
6.8.3 `b.hs 1b` 重新回到 1 执行sel的比较,如果相等,`2:CacheHit \Mode` 即命中了缓存中的方法,找到了缓存,进入到 CacheHit
6.7 `CacheHit` 分为 三种模式,`NORMAL`,`GETIMP`,`LOOKUP`;无论哪种,最终结果都是将去查找sel对应的imp,然后将其返回。