前言
當我們定義一段代碼:
Test *p = [Test alloc];
[p test1];
它在底層是如何實現(xiàn)的?通過clang進行編譯,可以找到:
Test *p = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test1"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test2"));
很明顯,OC在底層是通過objc_msgSend
傳遞消息的,第一個參數(shù)是接收對象
,第二個參數(shù)是sel_registerName() (ps:sel_registerName()==@selector()==NSSelectorFromString())
那么objc_msgSend
在底層又是如何實現(xiàn)的?
源碼解析
因為匯編更快的特性,所以objc_msgSend底層是由匯編實現(xiàn)的胀葱。
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
…………
END_ENTRY _objc_msgSend
1.檢查接收者
為不為空,為空結(jié)束
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
2.拿到類
和isa
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
3.查找cache
中是否有需要的方法
CacheLookup NORMAL, _objc_msgSend
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
- 地址偏移到
cache_t
在類中的位置#define CACHE (2 * __SIZEOF_POINTER__)
== 16字節(jié)
ldr p11, [x16, #CACHE] // p11 = mask|buckets
- 在
cache_t
左16位為mask
,右48位為buckets
先得到buckets
//與上0x0000ffffffffffff 得到buckets
and p10, p11, #0x0000ffffffffffff // p10 = buckets
因為cache_t分析中提到,方法在cache
中進行哈希獲取角標進行存儲荧缘。
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask;
}
所以先獲取mask
再與上sel
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
- buckets地址偏移到對應位置開始查找
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
PTRSHIFT
為3
,左移4
位及*16
,因為buckets
包含sel
和imp
,所以一個就是16字節(jié)
,+ ((_cmd & mask) << (1+PTRSHIFT))
則是地址偏移到對應位置戴而。
- 循環(huán)查找
- 循環(huán)條件:
sel != cmd
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
- 初始條件:當
bucket==buckets
,及當前bucket
為第一個bucket
時,跳轉(zhuǎn)到最后一個bucket
,逆序查找
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
//逆序
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
//mask==總占用-1,mask左移16位,則為總buckets大小
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
- 如果找到,則返回,沒有找到則進入
CheckMiss
,及進入慢速查找流程_lookUpImpOrForward
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
…………
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
…………