前言
我们都知道OC是一门动态的语言,它的核心就是rutime机制。而消息发送objc_msgSend可谓是rutime机制的基石。下面就让我们通过源码来揭开objc_msgSend的神秘面纱。
准备工作
源码解读
来自objc818 ps:耐心地一行行解读
objc_msgSend方法入口
ENTRY _objc_msgSend//方法入口
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 //p0 receiver 和 0比较
#if SUPPORT_TAGGED_POINTERS //__LP64__ 64位系统 支持taggedpointer类型
b.le LNilOrTagged //判断上面的cmp小于等#0,执行标号 LNilOrTagged
#else
b.eq LReturnZero //判断上面的cmp等于#0,即消息接收者 = nil,执行标号LReturnZero
#endif
ldr p13, [x0] // p13 = isa 把isa存进p13寄存器
GetClassFromIsa_p16 p13, 1, x0 // 执行标号GetClassFromIsa_p16获取class并存进p16寄存器
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // 判断上面的cmp等于#0,执行标号LReturnZero
GetTaggedClass // 不为0,执行标号GetTaggedClass,获取class指针
b LGetIsaDone // 跳转到标号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
流程分析:
- 比较p0 和 0,p0是第一个参数receiver,为空,LReturnZero
- 从isa读取class,分为俩种情况(TaggedPointer,Non-Pointer)
- LGetIsaDone->CacheLookup
TaggedPointer读取class
ps:这里值得扩展一波,taggedPointer优化了小值对象的存储
// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass
and x10, x0, #0x7 // x10 = small tag
asr x11, x0, #55 // x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
cmp x10, #7 // tag == 7?
csel x12, x11, x10, eq // x12 = index in tagged pointer classes array, negative for extended tags.
// The extended tag array is placed immediately before the basic tag array
// so this looks into the right place either way. The sign extension done
// by the asr instruction produces the value extended_tag - 256, which produces
// the correct index in the extended tagged pointer classes array.
// x16 = _objc_debug_taggedpointer_classes[x12]
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ldr x16, [x10, x12, LSL #3]
.endmacro
Non-Pointer指针读取class
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA //arm64 & !__LP64__ ps:也就是32位的数据模型
// Indexed isa
mov p16, \src //src是参数p13,把isa存进p16 // 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//读取classes数组所在page到x10
add x10, x10, _objc_indexed_classes@PAGEOFF//x10 = x10+偏移量,classes数组地址
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index 所属class的index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array 读取class地址到p16
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src //不需要验证,读取isa到p16
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address //执行标号ExtractISA
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
ExtractISA 核心代码 isa & isa_mask
.macro ExtractISA
and $0, $1, #ISA_MASK //isa & isa_mask 存入参数0也就是p16
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
autda $0, x10
#endif
.endmacro
CacheLookup
此处代码已精简(删除不同cpu架构分支,保留arm64 & LP64)
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // 把class存进x15
LLookupStart\Function:
/**
第一步,获取cache
*/
// p1 = SEL, p16 = isa
// p11 = mask|buckets,class指针平移16字节得到cache首地址,
//也是_bucketsAndMaybeMask首地址
ldr p11, [x16, #CACHE]
/**
第二步,获取buckets
*/
and p10, p11, #0x0000fffffffffffe // p10 = buckets = p11 & bucektMask
tbnz p11, #0, LLookupPreopt\Function //buckets为0,查找共享缓存
/**
第三步,计算index
*/
//下面俩行是cache_hash算法 ,index = (_cmd ^ (_cmd >> 7)) & mask
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
/**
第四步,获取index对应的的bucket地址
*/
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((index) << (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
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
cache_hit
.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
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
imp = imp ^ cls 从bucket的imp中解码出真正的imp,并执行
objc_msgSendUncached
慢速查找流程,又是很大一个篇幅,下篇再说
网友评论