前言
上一篇objc_msgSend讲到了通过isa拿到class,那么接下来又做了哪些操作,让我们一起来探索一下这一部分的源码。
Tips: 我使用的是818.2版本的源码。
CacheLookup
上一篇看到GetClassFromIsa_p16
,这里获取到class,后面一句就是LGetIsaDone
。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
复制代码
可以看到这里就一句代码,调用CacheLookup
指令,那么去看看这里面的代码有些什么,先全局搜索,找到定义的地方。可是看到代码,当场懵逼,一共200多行代码,让人望之生怯。调整心态,挨个看看里面怎么实现的,仔细看会发现里面有很多判断,那我们把符合我们探索条件的筛选出来(按照真机64位)。
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16
LLookupStart\Function:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
add p13, p10, p12, LSL #(1+PTRSHIFT) // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP
cmp x16, x15
cinc x16, x16, ne // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
复制代码
最后整理得到这些代码,这些是主要代码,其他的对我们学习意义不大。汇编语言,挨个看吧。
这里绘出了一个大致流程,不一定精确,但是能大概知道这里是什么操作。
Tips:
在objc_msgSend
里有很多如CONFIG_USE_PREOPT_CACHES
之类的判断,需要自己去查找到对应的定义。
也有很多__has_feature(ptrauth_calls)
的判断,这个其实就是一个判断手机是否为A12及以上系列的,也就是iPhone X
之后的手机。
总结
objc_msgSend
为什么要使用汇编语言,其实说到底就是为了更高的效率、更快的速度查找到要调用的方法,因为在一个工程里可能会有成千上万个方法,所以要尽可能的提高查找速度,提升性能。这里其实就是一个方法的快速查找,如果方法快速查找没有找到,那么就会进入一个慢速查找。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END