既然知道了方法的本质就是发送消息,那咱们继续研究一下runtime的消息查找markdown
runtime的消息查找分为2步:多线程
objc_msgSend
是用汇编写的,那咱们就从汇编开始探索一下objc_msgSend
都作了些什么函数
延伸:为何
objc_msgSend
是用汇编而不是用C编写的呢?oop
objc-msg-arm64.s
来到源码中,找到objc-msg-arm64.s
,再找到ENTRY _objc_msgSend
。post
个人天啊!这都是些什么鬼?性能
不要紧,我也看不懂,咱们边百度指令,边分析注释,硬读吧~spa
GetClassFromIsa_p16
搜索看一下这个方法,是怎么经过isa拿到class的: 的确看到了熟悉的代码
ISA_MASK
,就是经过isa & ISA_MASK
运算,拿到class,而后走到 CacheLookup
方法线程
CacheLookup
先看一下注释内存,咱们通常用到都是
NORMAL
模式。这时,咱们已经拿到了sel
和class
指针
(吐槽一下,这段代码太长了,截个图费劲巴拉的) 咱们上一段代码分析一段:
ldr p11, [x16, #CACHE] // p11 = mask|buckets
复制代码
x16
将类对象内存地址平移16位赋值给p11。咱们以前研究过,平移16位恰好就是缓存cache。其实后面注释就有了解释
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
复制代码
p11
经过and = &
与运算,拿到缓存中的buckets
赋值给p10
p11
经过LSR
右移48位,获得mask
,和sel
进行与运算,赋值给p12
_cmd & mask
和cache_t
中的cache_hash
方法同样,通过哈希运算以后,获得了 bucket 结构体指针add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
复制代码
p12
经过LSL
左移(1+PTRSHIFT)
,而后和p10
进行与运算,赋值给p12
把p12
的imp
赋值给p17
,sel
赋值给p9
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
复制代码
比较p9
和p1
,就是对比咱们传进来的方法编号,是否和缓存中找到的匹配,匹配就是缓存命中CacheHit
返回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
复制代码
若是bucket->sel == 0
,就跳到CheckMiss
方法
p10
和p12
进行比较,若是eq 相等
,就跳到第三步
若是不相等,p12
的指针进行--
操做,拿到新的sel
、imp
再跳到1第一步,进入循环,从新执行一遍
// 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
复制代码
这里再执行一遍1~3的流程,至关于给了一个容错的机会,若是第二次仍是找不到咱们须要的sel
对应的imp
,就跳到JumpMiss
方法,开始进入慢速流程。
咱们再看一下流程中间碰见CheckMiss
方法、JumpMiss
方法
CheckMiss
和JumpMiss
cbz
比较,若是结果为0就跳转后面
由于咱们是 NORMAL
模式,因此无论进哪一个方法都会来到 __objc_msgSend_uncached
方法
__objc_msgSend_uncached
__objc_msgSend_uncached
方法中最核心的逻辑就是 MethodTableLookup
方法,意为查找方法列表。
MethodTableLookup
大体一看,又要计算?咱们直接抓住核心的点:bl _lookUpImpOrForward
,跳转了这个方法,全局搜索一下_lookUpImpOrForward
发现并无。那搜索一下lookUpImpOrForward
,有这个方法!
其实,咱们这里去掉下划线找方法,属于开启了上帝视角。若是按正常流程,咱们应该打开汇编,断点方法,看汇编里面的jump
和callq
命令都走了哪些方法
由于lookUpImpOrForward
这个方法是一个C/C++方法,它的参数必须是肯定的,这样就能够解释通bl _lookUpImpOrForward
这行代码前面的操做了,就是为了传入肯定的参数作准备。
快速流程就到这里,就是在缓存中经过sel找imp,找不到就进入慢速流程。
咱们在上一篇章中验证过,方法存储在 类 -> bit -> rw -> ro -> methodList
里面,带着这个思路,看一看runtime在源码中查找消息的流程是否是如出一辙的?
lookUpImpOrForward
哇,这里面又臭又长,咱们就简述一下,讲一下几个须要注意的点:
有一步容错
,经过cache_getImp
方法,若是找到了imp
就直接返回
细节点
runtimeLock.lock();
在这里加锁,防止同时访问2个方法,出现imp返回错误checkIsKnownClass(cls);
判断这个类是不是被编译过的,若是不是就输出错误信息若是是对象方法,就在当前类的方法列表中使用二分查找法
寻找imp
,找到就进行缓存,而后返回imp
判断,若是当前类,没有父类,直接崩溃并打印错误信息
若是有父类,直接在父类的缓存中找
imp
二分查找法
寻找imp
,找到就进行缓存,而后返回imp
若是父类中没有,就在元类(父类的父类)中找,继续上一步的循环
最后都没找到,就进行动态方法解析resolveMethod_locked(inst, sel, cls, behavior);
在上面的流程中有一点要提一下,_objc_msgForward_impcache
这个方法,咱们去看一下~
c函数没找到这个方法,汇编中找到了。
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
复制代码
继续找__objc_msgForward
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
复制代码
c函数__objc_forward_handler
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
复制代码
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
复制代码
这个找到最后,好熟悉!这不就是找不到实现方法崩溃,在控制台打印的信息吗!!!
类方法 - 本身有
类方法 - 本身没有 - 找父类的
类方法 - 本身没有 - 父类也没有 - 找父类的父类 - NSObject
类方法 - 本身没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃
类方法 - 本身没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 可是有对象方法
消息查找阶段:
快速流程
,拿到isa
,经过汇编
的手段在缓存
中找,找到就返回慢速流程
,经过:当前类.方法列表 -> 父类.缓存 -> 父类.方法列表 -> 元类.缓存 -> 元类.方法列表 这个流程,哪一步找到就返回