1 引入
iOS方法调用,底层首先会查找cache进行快速查找imp
查找cache进行快速查找imp的具体流程逻辑请参考:iOS-13.方法查找流程之快速查找流程
如果快速查找cache没有找到匹配的imp,则会跳转到__objc_msgSend_uncached
,进入慢速的方法列表查询 MethodTableLookup
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup // 查询方法列表
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
MethodTableLookup是个宏,具体定义如下:
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
从MethodTableLookup的宏定义
可知,主要是进行了一系列的寄存器操作,然后跳转到_lookUpImpOrForward
,但全局搜索_lookUpImpOrForward未找到其定义,猜想底层从汇编跳转到C源码部分
注:从汇编宏跳到C函数,函数名少一个下划线前缀
从C函数调用汇编宏,名称会多一个下划线前缀
全局搜索lookUpImpOrForward
果然找到了函数的定义
2 验证程序跳转流程
- main.m设置断点,打开
Debug->Debug Workflow->Always Show
Disassemble - 运行程序后,执行到断点处,
按住control键+点击step into
,多点击step into直到进入_objc_msgSend_uncached
,可见一堆的寄存器处理后,进行了callq
- 程序确实进入
lookUpImpOrForward at objc-runtime-new.mm:6099
让编译忽略错误
3 lookUpImpOrForward慢速查找逻辑
3.1 lookUpImpOrForward源码
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 定义的消息转发forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// 多线程可能更新了cache 所以进入慢速查找前会先进入快速查找
if (fastpath(behavior & LOOKUP_CACHE)) {
/*
cache_getImp将调用汇编:
```
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
```
注意CacheLookup GETIMP查找失败后,跳转到LGetImpMiss,将寄存器清零直接返回,不会再次进入慢速查找
*/
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//加锁,目的是保证读取的线程安全
runtimeLock.lock();
//判断是否是一个已知的类:判断当前类是否是已经被认可的类,即已经加载的类
checkIsKnownClass(cls);
//判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方法后续的循环
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
//判断类是否初始化,如果没有,需要先初始化
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
//查找类的缓存
// unreasonableClassCount 最大循环次数
for (unsigned attempts = unreasonableClassCount();;) {
//当前类方法列表(采用二分查找算法),如果找到,则返回,将方法缓存到cache中
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
//赋值当前类 = 当前类的父类,并判断父类是否为nil
if (slowpath((curClass = curClass->superclass) == nil)) {
//未找到方法实现,方法解析器也不行,使用转发
imp = forward_imp;
break;
}
// 如果attempts==0,则停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 父类缓存 此时的curClass已在前面代码赋值为当前类的父类--cache_getImp进入汇编查找,如果没查找到,直接返回,不会进入lookUpImpOrForward
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 到继承链找到nil后未找到,则将imp = forward_imp 跳出循环
break;
}
if (fastpath(imp)) {
//如果在父类中,找到了此方法,将其存储到cache中
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;
}
return imp;
}
3.2 lookUpImpOrForward流程逻辑
- 1)定义的消息转发
forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
-
2)进入慢速查找后,
多线程
可能更新了cache
所以进入慢速查找前会先进入快速查找
-
3)
cache_getImp
将调用汇编 执行CacheLookup GETIMP, _cache_getImp
注意
CacheLookup GETIMP
查找失败后,跳转到LGetImpMiss
,将寄存器清零直接返回,不会再次进入慢速查找
-
4)进入循环查找流程是
- 当前类方法列表
- 将当前类 赋值为当前类的父类进行迭代(判断父类是否为nil,为nil就 imp = forward_imp消息转发,退出循环)
- 如果--attempts==0,则停止循环
- 查找快速父类缓存,在继承链的父类中如果找到了此方法,跳出循环,将方法存储到cache中
-
5)没有找到方法实现,尝试一次动态方法解析,并返回动态方法解析的结果
-
6)找到了方法实现,则将方法写入到缓存
log_and_fill_cache --> cache_fill
3.3 在当前类方法列表中查找imp(采用二分查找算法): findMethodInSortedMethodList
getMethodNoSuper_nolock --> 循环methods调用search_method_list_inline(*mlists, sel) --> findMethodInSortedMethodList
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
/**
二分查找
list 递增存储的
*/
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// list中可能存在同名方法,方法的存储顺序是: 分类 - 类
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
//排除分类重名方法(方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
//如果是两个分类,就看谁先进行加载
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
3.4 总结
1)对象方法查找测试
- 自己有,就找到后返回;
- 自己没有,找父类,父类找到后返回;
- 自己没有,父类没有,找父类的父类,找到后返回;
- 沿着继承链找父类,直到NSObject,找到就返回;
- 如果继承链中都没有找到,崩溃
2)类方法查找测试
- 自己有,就找到后返回;
- 自己没有,找父类,父类找到后返回;
- 自己没有,父类没有,找父类的父类,找到后返回;
- 沿着继承链找父类,直到NSObject,找到就返回;
- 沿着继承链找父类,直到NSObject都没有找到,找NSObject的对象方法,找到后返回;
- 沿着继承链找父类,直到NSObject都没有找到,找NSObject的对象方法,也没有找到,则崩溃
4 cache_getImp解析
快速查找失败,未找到imp后,为了规避多线程
的影响,重新刷新了类的cache,在进入C代码的 lookUpImpOrForward
中后还进行了一次快速查找cache中的imp:imp = cache_getImp(cls, sel);
cache_getImp
将进入汇编代码,_cache_getImp
,源码如下,从源码可知核心为CacheLookup GETIMP, _cache_getImp
进入CacheLookup宏(进入CacheLookup宏参考:iOS-13.方法查找流程之快速查找流程)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
CacheLookup GETIMP
,找到imp则直接返回imp,如果查找失败后,跳转LGetImpMiss
,将寄存器清零直接返回,不会再次进入慢速查找
5 _objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
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_msgForward_impcache
-->__objc_msgForward
-->_objc_forward_handler
进入C语言函数void *_objc_forward_handler = (void*)objc_defaultForwardHandler
- 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);
}
动态方法决议,请关注后续更新:iOS-15.objc_msgSend动态方法决议和消息转发