1. Runtime
Objective-C Runtime Programming Guide
Runtime就是使用C,C++和汇编混合而成的,为OC提供运行时特性的一套机制。
2.Runtime底层
首先通过Clang编译main.c文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person sayHello];
}
return 0;
}
在mian.cpp文件中,我们可以看到:
1.OC层的方法调用会编译成objc_msgSend(消息接收者,sel_registerName("XXX"))
2.所以@selector(XXX)等价于sel_registerName("XXX")
3.[person sayNB]等价于objc_msgSend(person,sel_registerName("sayNB"))
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
3.objc_msgSend的快速查找
通过上一篇对cache_t的结构分析,我们知道(_sel,_imp)是存储在cache_t下的buckets的哈希表中
objc_msgSend会先从内存中查找,通过汇编快速查询到IMP,下面我们去源码里面看它的查找过程
我们通过源码找到objc-msg-arm64.s文件里面
// enter 开始进入 _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
// 获取isa
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 从缓存里面获取imp
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
// 小对象类型
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
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
CacheLookup的实现:
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
// #define CACHE
// 所以p11指向了cache_t的首地址
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// p10 = p11 & 0x0000ffffffffffff
// p10 指向cache_t下的buckets
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
// 内存平移
// (1+PTRSHIFT):(1+3)
// p12 = buckets + ((_cmd & mask) << (4))
// 为什么左移4位, 因为bucket存了(SEL,IMP) ,恰好总共16字节。
// 所以得到buckets[1]
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
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
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
大致过程:
1.首先判断方法接受者p0存不存在.
2.通过对象的isa获取class
3.通过类的指针平移16字节,获取到cache.
4.通过cache,通过计算得到buckets,mask以及index
5.通过下标index获取buckets哈希表下标为index的值bucket_t
6.将bucket.sel与_cmd对比,如果一致,则返回bucket.imp
通过对汇编源码的分析,得到通过汇编在内存中查找IMP的流程