在上一篇文章中介绍了在汇编部分的缓存快速查找流程。由于首次调用
或者缓存扩容
等问题导致的缓存查找失败,就需要进入慢速查找流程
.
CheckMiss\JumpMiss
这个macro中并且走到__objc_msgSend_uncached
这个函数中。 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
MethodTableLookup
的实现,发现实现很长,但是都是一些寄存器的操作等,只看我们关心的b、bl
.macro MethodTableLookup
...
// 省略影响跟流程的代码
bl _lookUpImpOrForward
...
_lookUpImpOrForward
发现找不到对用的实现了。苹果编码有一个习惯通过前缀增加、减少_
,来实现多种语言的切换。
通过调用lookUpImpOrForward
,就来到了c++部分。
前文中的流程跳转有一部分猜测的成分,现在通过调试来验证一下之前的猜测.
__objc_msgSend_uncached
lookUpImpOrForward
,而且给出了c++函数的位置通过编译调试进一步验证了之前的猜测。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//参数准备
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
//再进行一次缓存的查找
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock(); //加锁
// TODO: this check is quite costly during process startup.
//判断该类是否是已知类
//这个操作需要从已知类表中做耗时的查找动作;推测以后苹果会优化点
checkIsKnownClass(cls);
//判断当前类是否加载到内存中(isa和rw信息是否存在)。
if (slowpath(!cls->isRealized())) {
//如果不存在进行类的加载,并且会递归加载所有父类、元类。为了确定类的父类、isa链
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
//判断类是否初始化,同样是递归判断所有
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
//对类进行初始化,并且会递归加载所有父类、元类。为了确定类的父类、isa链
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked(); //解锁
//循环查找,无跳出条件
for (unsigned attempts = unreasonableClassCount();;) {
//使用二分法在当前类的方法列表methodList中查找该方法
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//本质就是查找方法的imp实现
imp = meth->imp;
goto done;
}
//1. 将当前类切换为父类
//2. 如果当前类的父类是nil,也就是在NSObject中都没有找到则直接跳出循环
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
// 如果父类链中存在循环,则停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
//对父类进行方法查找
//1.汇编缓存快速查找
imp = cache_getImp(curClass, sel);
//如果父类没找到,首次会进入动态方法解析
if (slowpath(imp == forward_imp)) {
break;
}
//在父类中找到了该方法
if (fastpath(imp)) {
goto done;
}
}
//每个类首次都会进行一次方法动态解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//取反,保证该方法只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//存储到缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
//解锁
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
//返回查询结果imp
return imp;
这就是整个消息imp查询闭环流程(快、慢)。
objc_msgSend慢速流程.png
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
.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
GETIMP
__objc_msgSend_uncached
,所以并不会产生递归,只会查找当前类的缓存
extern void _objc_msgForward_impcache(void);看到extern
就知道它又是汇编代码编写的了
STATIC_ENTRY __objc_msgForward_impcache
//跳转到 __objc_msgForward
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//定位
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
//寄存器操作
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
__objc_forward_handler
没有找到下文了,猜测它是c++编写,所以取一个_继续搜索以上已经解释了慢速查找的整个流程,现在对MethodList二分查找
的实现做一个解释。(核心方法findMethodInSortedMethodList
)
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
//当前list是有序的,在类创建的时候就已经进行了加载
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key; //key 等于 say666
uint32_t count;
//base相当于low,count是max,probe是middle,这就是二分
for (count = list->count; count != 0; count >>= 1) {
//从首地址+下标 --> 移动到中间位置(count >> 1 右移1位即 count/2 = 4)
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
//如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
if (keyValue == probeValue) {
// -- while 平移 -- 找出分类重名方法
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
//找出分类重名方法
//如果是两个分类,就看谁先进行加载
probe--;
}
return (method_t *)probe;
}
//如果keyValue 大于 probeValue,就往probe即中间位置的右边查找
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
示例图:
8中找1
8中找7