cache_t的底层分析续集
在cache_t的底层分析已经知道Cache_t的底层结构, 了解在inset中buckets的创建机制。很好奇的是何时调用的insert的呢?这也是今天的重点,探寻cache何时insert?
通过设置断点,查看调用栈的方式,寻找inset是何时被调用。
通过查看调用栈,会发现inset之前调用了log_and_fill_cache、lookUpImpOrForward、_objc_msgSend_uncache,可以看出,对象调用方法时,内部调用执行函数流程为:_objc_msgSend_uncache -> lookUpImpOrForward -> log_and_fill_cache -> inset。
全局查找_objc_msgSend_uncache
发现_objc_msgSend_uncache调用是在一个汇编文件中。(懵。。。, 先放一边,后面就迎刃而解)
我们现在清楚当调用[p saySomething]时,内部调用了_objc_msgSend_uncache,那我们使用clang将OC文件重写,看看底层C++的实现。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")) ;
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySomething"));
}
return 0;
}
复制代码
发现[p saySomething]在底层c++实现是调用了objc_msgSend这个函数。分析一下
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySomething"))
其中(void (*)(id, SEL))代表函数指针类型,返回值为void,需要2个参数,类型分别为id,SEL。
(void *)objc_msgSend其中objc_msgSend就是函数指针名,将objc_msgSend强转为(void )指针,然后再其强转为(void ()(id, SEL))函数指针。
((id)p, sel_registerName(“saySomething”))就是对象函数的参数列表。也就是id类型的p,以及通过sel_registerName得到的SEL。
由此可见,对象调用方法的底层是通过调用函数objc_msgSend,也就是消息发送。尝试直接使用底层RuntimeAPI给对象发消息。
调用结果一致。
言归正传,开篇通过查看cache的insert的调用堆栈,发现调用方法时,调用的_objc_msgSend_uncache是在汇编中被调用, 于是通过对OC文件的重写, 发现底层C++实现时,对象调用方法其实是调用了objc_msgSend函数。
下一步,探寻objc_msgSend
全局查找
发现objc_msgSend也在汇编文件中,由此可见objc_msgSend是结合了汇编实现(可能是为了运行效率吧)。
分析objc_msgSend汇编代码实现
ENTRY _objc_msgSend:ENTRY代表程序的入口,也就是_objc_msgSend的入口
UNWIND _objc_msgSend, NoFrame:UNWIND 就是一个栈帧寄存器,记录当前 栈顶的指针。
cmp p0, #0: cap 比较指令, 将po 也就是objc_msgSend的第一个参数,消息接受者的指针与0比较,
#if SUPPORT_TAGGED_POINTERS 当支持TAGGED_POINTERS类型
b.le LNilOrTagged 如果比较的数小于0 就执行LNilOrTagged
#else
b.eq LReturnZero 如果比较数等于0,就执行LReturnZero
#endif
ldr p13, [x0]// p13 = isa, ldr 代表加载数据, 也就是将当前x0寄存器中的值读入到p13。 x0此时就是消息接受者的指针, 也就是isa(对象的首地址存的就就是isa的地址)。
GetClassFromIsa_p16 p13, 1, x0 // p16 = class 掉了的GetClassFromIsa_p16 传入了3个参数
GetClassFromIsa_p16
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// 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__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
复制代码
SUPPORT_INDEXED_ISA由于当前不是arm架构,SUPPORT_INDEXED_ISA == 0, 所以我们分析 LP64,由于needs_auth 传进来的是1, 所以执行else。
ExtractISA p16, \src, \auth_address
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
复制代码
ExtractISA中, $1就是isa,然后和ISA_MASK进行与操作,也就得到了当前的class地址, 然后将class地址写入到$0,也就是p16。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
复制代码
得到class 之后,就执行 CacheLookup,传入了三个参数,其中包括我们在断点查看insert调用时,在堆栈中发现的__objc_msgSend_uncached方法。说明当对象发送消息时,也就是调用 _objc_msgSend中,内部的CacheLookup中,会有可能执行
__objc_msgSend_uncached,然后进行class的缓存添加。
总结
在分析cache_t后, 我们了解了cache_t的结构,以及insert中扩展buckets的机制,通过对insert前调用堆栈的查看, 发现对象调用方法在底层是通过执行 _objc_msgSend。进一步对 _objc_msgSend的分析,我们找到了 _objc_msgSend汇编实现,分析了 _objc_msgSend汇编实现逻辑,找到了在 CacheLookup中 有对 __objc_msgSend_uncached的调用。(未完待续,分析CacheLookup)