前言
当我们定义一段代码:
Test *p = [Test alloc];
[p test1];
它在底层是如何实现的?通过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
传递消息的,第一个参数是接收对象
,第二个参数是sel_registerName() (ps:sel_registerName()==@selector()==NSSelectorFromString())
那么objc_msgSend
在底层又是如何实现的?
源码解析
因为汇编更快的特性,所以objc_msgSend底层是由汇编实现的。
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.检查接收者
为不为空,为空结束
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字节
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字节
,+ ((_cmd & mask) << (1+PTRSHIFT))
则是地址偏移到对应位置。
- 循环查找
- 循环条件:
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
时,跳转到最后一个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
…………