iOS底层再探objc_msgSend

前言

上一篇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
        
复制代码

最后整理得到这些代码,这些是主要代码,其他的对我们学习意义不大。汇编语言,挨个看吧。

objc_msgsend_CacheLookup流程.png

这里绘出了一个大致流程,不一定精确,但是能大概知道这里是什么操作。

Tips:
objc_msgSend里有很多如CONFIG_USE_PREOPT_CACHES之类的判断,需要自己去查找到对应的定义。
也有很多__has_feature(ptrauth_calls)的判断,这个其实就是一个判断手机是否为A12及以上系列的,也就是iPhone X之后的手机。

总结

objc_msgSend为什么要使用汇编语言,其实说到底就是为了更高的效率、更快的速度查找到要调用的方法,因为在一个工程里可能会有成千上万个方法,所以要尽可能的提高查找速度,提升性能。这里其实就是一个方法的快速查找,如果方法快速查找没有找到,那么就会进入一个慢速查找。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享