今天主要是objc_msgSend源码分析:
ENTRY _objc_msgSend //进入_objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check 判断消息接收者是否为空 或者 为tagged pointer,
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero //若对象为空,则直接return 0,这也是空对象调用方法不崩溃的原因
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached 方法查找
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend //结束_objc_msgSend
其实流程看上去很清晰
1、判断消息接收者是否为空 或者 为tagged pointer
2、拿到isa,通过isa拿到class
3、然后在class中的缓存查找方法,找到就直接调用,没找到则走objc_msgSend_uncached
第二步
我们先来看看第二步中是怎么通过GetTaggedClass
拿到class的吧
//.macro宏定义的意思
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA //表示 isa_t 中存放的 Class 信息是 Class 的地址,还是一个索引(根据该索引可在类信息表中查找该类结构地址)。经测试,iOS 设备上 SUPPORT_INDEXED_ISA 是 0。
// Indexed isa
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__ //64位操作系统
.if \needs_auth == 0 // needs_auth是入参,传入的1
mov p16, \src
.else //所以会走入else,拿到类对象并放入p16
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
源码中我根据自我理解加上了注释,所以下一步,应该回到ExtractISA
中,继续看源码
.macro ExtractISA
and $0, $1, #ISA_MASK//isa& ISA_MASK得到类对象
.endmacro
就这样拿到类对象啦,好像也不是很复杂,在iOS-isa指向图&类结构(上)中,我们也用这样的方法取到类对象的呀。
第三步
接下来就是重点啦,看看他是如何查找缓存的吧,这部分源码偏多,我们分两部分看吧
第一部分
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
/*
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
*/
//由上方宏定义可以得出真机环境下 CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//这里ifelse过多,我们分级处理一下,这样看着会清晰一点
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS //这里不看
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //所以我们直接走到这里
ldr p11, [x16, #CACHE] // p11 = mask|buckets 高16位为mask,低48位为buckets //#define CACHE (2 * __SIZEOF_POINTER__) 所以cache为16 x16为class,平移16拿到熟悉的cache_t
#if CONFIG_USE_PREOPT_CACHES //真机情况下为1
#if __has_feature(ptrauth_calls) //a12走这里,判断p11的0号位置,不为0进入LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function//LLookupPreopt查找共享缓存
and p10, p11, #0x0000ffffffffffff // p10 = buckets //取低48位的数据也就是取到我们之前探究过的存储在类对象中的cache_t中的,buckets,这是一个结构体,里面有sel,imp
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
//下面两句对应cache_hash方法,即p12存的是index
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 //这里不看
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
/*
接着就到这里啦,我们把源码注释提取出来,很明显这就是一个do循环了,找到就调用或返回imp
// do {
if (sel != _cmd) {scan more}
else { hit: call or return imp}
if (sel == 0) goto Miss;
} while (bucket >= buckets)
*/
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 相当于buckets(i) p13就是要查找的buckets
// 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
第二部分
CacheHit源码
.macro CacheHit
.if $0 == NORMAL //找到后,传入的参数是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 // cached imp & isa 编码拿到真是imp
br $0 //调整执行imp
.endmacro
流程图
image.png
总结
快速查找流程中有许多通过掩码,位移计算的操作,其实与cache_t
中insert
方法的流程都是一一对应的,比如hash拿到index,最后通过cache imp & isa
拿到imp。感兴趣的朋友可以一一验证一下。
贴一下大佬的流程图,详细很多
image.png
参考:https://www.jianshu.com/p/89ab04a91cbc