通过这篇文章可以获得什么
- 汇编到源码的执行流程
- __objc_msgSend_uncached(慢速查找流程的起因)
- 为什么要执行慢速查找流程,全部使用汇编进行快速查找不爽吗?
- 为什么快速查找缓存要使用汇编编写,而不是用C++编写呢?
- MethodTableLookup都做了哪些事情?
- TailCallFunctionPointer
- lookUpImpOrForward(慢速查找流程核心分解)
- 遍历methodList之前都做了哪些事情?
- findMethodInSortedMethodList二分法分析整理
- 二分法查找Demo
- 二分法查找图解
- 消息慢速查找流程图
消息的慢速查找探索起因
在cache内查找bucket_t的过程中,如果查找了所有的缓存也无法命中的时候,接下来就要进入消息的慢速查找流程了,也就是由汇编查找 -> C/C++代码查找。
汇编到源码的执行流程
-
第一步: 在
CacheLookup
汇编中,当无法命中
缓存的时候,会执行MissLabelDynamic
,也就是__objc_msgSend_uncached
-
第二步: __objc_msgSend_uncached会执行MethodTableLookup和TailCallFunctionPointer x17
-
第三步:
MethodTableLookup
执行四个指令,将x16
移到x2
,将#3
移到x3
,跳转lookUpImpOrForward
,将x0
移到x17
-
第四步:
lookUpImpOrForward
,找到关键代码,是一个死循环查找for (unsigned attempts = unreasonableClassCount();;)
-
第五步:
加锁
、进入二分法
查找 -
第六步:如果找到,
log_and_fill_cache
,将找到的imp
插入缓存,下一次进行快速查找
-
第七步:如果找不到,通过
isa
的查找链一直向上查找:共享缓存
->methodList
->父类
->NSObject
->nil
– >跳出循环
。最终都找不到的话,也就是superclass=nil
的时候,将imp = forward_imp
,进入消息转发流程。
可以通过慢速查找流程找到IMP并插入缓存的图解:
__objc_msgSend_uncached(慢速查找流程的起因)
带着问题探索源码:
__objc_msgSend_uncached
此方法是进入慢速查找流程的起因
,此方法是作为参数传递
进CacheLookup
的,如果CacheLookup查找不到imp
的时候会执行MissLabelDynamic
,也就是执行了__objc_msgSend_uncached
,此方法非常简单:
-
MethodTableLookup
调用C++
代码执行慢速
查找流程 -
TailCallFunctionPointer x17
拿到上一步
的查找结果跳转到x17寄存器
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
//调用C++代码执行慢速查找流程
MethodTableLookup
//跳转到x17寄存器,等待汇编后续逻辑处理
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
复制代码
MethodTableLookup
保存信息方法,已知x0
是receiver
,x1
是_cmd
,即sel
,先将x16
(FFPerson类)存在x2
寄存器,将3
存在x3
寄存器,然后跳转至关键方法lookUpImpOrForward
,这个方法返回值在汇编的角度
来看是储存在x0
寄存器的,所以当lookUpImpOrForward
执行完成,将返回值x0
存入x17
.
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, behavior)
// receiver and selector already in x0 and x1
//lookUpImpOrForward方法需要4个参数:
//obj = x0
//sel = x1
//cls = x2
//behavior = x3 = 3
mov x2, x16
mov x3, #3
//bl:跳转指令,并将回家的路存在x30寄存器(lr)
bl _lookUpImpOrForward
// 经过_lookUpImpOrForward拿到的返回值(IMP)将存在x0位置
// 将x0赋值给x17
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
复制代码
TailCallFunctionPointer
//A12以上芯片(支持arm64e结构)
#if __has_feature(ptrauth_calls)
//通过慢速查找流程找到了IMP,那么这里就是将跳转至寄存器x17
//以提供后续汇编继续执行
.macro TailCallFunctionPointer
// $0 表示的是参数x17
braaz $0
.endmacro
#else
.macro TailCallFunctionPointer
// $0 表示的是参数x17
br $0
.endmacro
复制代码
结论:
当快速查找流程无法命中的时候会进入
慢速查找流程
慢速查找流程
__objc_msgSend_uncached
会执行2
条指令,MethodTableLookup去查找并返回imp
,TailCallFunctionPointer
跳转至x17
寄存器,也就是imp
所在的寄存器慢速查找流程将会进入
C++
中执行,然后通过x0寄存器返回,继续在汇编中向下执行,方法查找流程最终会跳转至x17寄存器(无论是慢速查找还是快速查找。
结论图解:
lookUpImpOrForward(慢速查找流程核心)
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//创建forward_imp,并给定默认值_objc_msgForward_impcache
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
//创建imp,用于接收通过sel查找的imp
IMP imp = nil;
//创建要查找的类,这个类通过isa的指向关系是会一直变化的,
//直到最终指向NSObject的父类nil为止
Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
/**
发送到类的第一条消息通常是 +new 或 +alloc 或 +self
但是,此时该类尚未初始化,此时behavior = 3|8 = 11
当向将上+new等这些方法inset进缓存的时候
不满足behavior & LOOKUP_NOCACHE) == 0这个条件,8 & 11 = 8
所以上述这些方法不会加载进缓存。
如果类已经初始化了,就不会修改behavior的值了,behavior=3
我们自定义的方法是可以正常加载进缓存的。
*/
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock 在 isRealized 和 isInitialized 检查期间被持有
// 防止与并发实现竞争。
runtimeLock.lock();
//检查类是否被注册了
checkIsKnownClass(cls);
/**初始化跟cls实例对象在isa指向图中的每一个类(class和metaClass)
以便后续自己类里面找不到方法去父类里面找
依次向上找
所以在此处对所有相关的类进行了初始化
*/
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
//curClass为当前实例对象的类
curClass = cls;
/**
* 循环查找类对象的methodList,当前类没有的话就找父类
* 父类没有就找父类的父类,一直找到NSObject类
* 如果NSObject都找不到的话最终curClass会指向nil
* 将事先准备好的forward_imp赋值给imp
* 然后结束慢速查找流程,接下来进入Runtime消息转发机制
*/
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
/**
* 第一步先找共享缓存里面有没有我们的方法
* 通常情况下我们的自定义方法不会出现在共享缓存中
*/
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
/**
*在当前类的方法列表里面查找,这是重点
*查找算法是二分法
*/
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
/**
*如果当前类找不到,取将curClass指向superclass
*查询父类的methodList,一直找到NSObject的父类nil为止
* 将事先准备好的forward_imp赋值给imp
* 然后结束慢速查找流程,接下来进入Runtime消息转发机制
* 结束循环遍历
*/
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// 类列表中的内存损坏
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 在父类的缓存中查找,这里再次进入汇编查找流程
imp = cache_getImp(curClass, sel);
//如果没有找到,将默认的forward_imp赋值给imp
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
//如果找到了
if (fastpath(imp)) {
//将找到的method插入到缓存中,以便下次查找使用快速缓存查找
goto done;
}
}
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
//找到了sel对应的imp,将method方法加载进缓存
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
复制代码
realizeClassWithoutSwift
只列出了2行
关键源码,用来表示递归
初始化类和元类
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
}
复制代码
getMethodNoSuper_nolock(二分查找流程)
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
//获取methodList,methodList因为数据类型的原因可能为二维数组
//循环条件是数组不为空,即开始位置不等于结束位置
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
//进入search_method_list_inline修复为有序list
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
复制代码
search_method_list_inline
修复函数,来函是methodList变得有序
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
return nil;
}
复制代码
findMethodInSortedMethodList(已经排好序的methodlist)
isSmallList代表的是m1
的电脑。
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {
if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
}
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
}
}
复制代码
findMethodInSortedMethodList(二分查找真正的实现方法)
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
//开始位置:0
auto first = list->begin();
//base开始也为0
auto base = first;
//probe也为0
decltype(first) probe;
//要查找imp对应的sel
uintptr_t keyValue = (uintptr_t)key;
//list的个数
uint32_t count;
/**
* 举例:假设要查找的sel在第7位
* 首先count = list.count,这里假定count=8
* 进入循环,probe = base + ( 8 >> 1 ) = 0 + 4 = 4
* 那么第一次查找的范围就是4-8,匹配元素位置是4,判定结果keyValue(7)> prebeValue(4),未匹配
* 满足keyValue > probeValue,base = probe + 1 = 4 + 1 = 5,count-- = 7
* 第二次进入循环,此时count = 7 >> 1 = 3, probe = 5 + 3 >> 1 = 6
* 第二次查找的范围是6-7,匹配元素位置是6,判定结果keyValue(7)> prebeValue(6),未匹配
* 满足keyValue > probeValue,base = probe + 1 = 6 + 1 = 7,count-- = 2
* 第三次进入循环,此时count = 2 >> 1 = 1, probe = 7 + 1 >> 1 = 7
* 第三次查找的元素是7,匹配,返回imp
*/
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
//向前寻找第一个出现的imp,为了避免分类方法于主类方法相同时问题
//这也就是为什么分类方法会被加载的原因
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
二分法查找Demo
案例代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
auto first = 0;
auto base = first;
decltype(first) probe = 0;
uintptr_t keyValue = 7;
uint32_t count;
for (count = 8; count != 0; count >>= 1) {
NSLog(@"前=count:%u---probe:%u",count,probe);
probe = base + (count >> 1);
NSLog(@"后=count:%u---probe:%u",count,probe);
if (keyValue == probe) {
NSLog(@"找到了=keyValue:%lu---probe:%u",keyValue,probe);
}
else{
NSLog(@"没找到=keyValue:%lu---probe:%u",keyValue,probe);
}
if (keyValue > probe) {
base = probe + 1;
count--;
}
}
}
return 0;
}
复制代码
控制台打印结果:
2021-07-02 04:06:34.275300+0800 001-二分法实践[8192:538571] 前=count:8---probe:0
2021-07-02 04:06:34.276175+0800 001-二分法实践[8192:538571] 后=count:8---probe:4
2021-07-02 04:06:34.276236+0800 001-二分法实践[8192:538571] 没找到=keyValue:7---probe:4
2021-07-02 04:06:42.015297+0800 001-二分法实践[8192:538571] 前=count:3---probe:4
2021-07-02 04:06:42.015397+0800 001-二分法实践[8192:538571] 后=count:3---probe:6
2021-07-02 04:06:42.015442+0800 001-二分法实践[8192:538571] 没找到=keyValue:7---probe:6
2021-07-02 04:06:43.917440+0800 001-二分法实践[8192:538571] 前=count:1---probe:6
2021-07-02 04:06:43.917615+0800 001-二分法实践[8192:538571] 后=count:1---probe:7
2021-07-02 04:06:43.917669+0800 001-二分法实践[8192:538571] 找到了=keyValue:7---probe:7
复制代码
二分法查找图解
操作步骤:
举例:假设要查找的sel对应的在第
7
位首先
count = list.count
,这里假定count=8
第一次
进入循环,probe
= base + ( 8 >> 1 ) = 0 + 4 =4
那么第一次查找的范围就是
4-8
,匹配元素位置是4
,判定结果keyValue(7)> prebeValue(4),未匹配
满足keyValue > probeValue,
base
= probe + 1 = 4 + 1 = 5,count– =7
第二次
进入循环,此时count
= 7 >> 1 =3
,probe
= 5 + 3 >> 1 =6
第二次查找的范围是
6-7
,匹配元素位置是6
,判定结果keyValue(7)> prebeValue(6),未匹配
满足keyValue > probeValue,
base
= probe + 1 = 6 + 1 =7
,count– =2
第三次
进入循环,此时count = 2 >> 1 = 1, probe = 7 + 1 >> 1 = 7第三次查找的元素是
7
,匹配,返回imp
log_and_fill_cache(缓存填充)
如果记录器允许,调用cache的insert
方法,将其插入缓存
,下一次的查找就会进行快速缓存查找
了。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
复制代码
结论
lookUpImpOrForward,此过程为慢速查找流程,通过死循环的方式不断遍历来查找imp
,对于全局性来讲,首先去找共享缓存
,然后查自己的methodList
,如果自己没有,找父类
,父类没有,找NSObject
,NSObject没有,会指向nil
,最终跳出来。
流程简化:共享缓存
-> methodList
-> 父类
-> NSObject
-> nil
– >跳出循环
消息慢速查找流程图
解答在源码探索过程中提出的疑问点:
为什么快速查找缓存要使用汇编编写,而不是用C++编写呢?
汇编
更接近机器的语言,执行效率
非常的快,特别的安全
,这是优点。但是由于方法是特别的多,通过汇编查找缓存的方式可以最大程序的优化
执行效率。另外,汇编在执行的时候,参数
可以是未知的
,这一点区别与C/C++
,参数一定是调用前就已经指定
好了,明确参数
,汇编的另一优点也就体现了,相对于C/C++更加的动态化
。
为什么要执行慢速查找流程,全部使用汇编进行快速查找不爽吗?
因为在缓存中已经找不到
了,进入了慢速查找流程,这个时候需要去找方法
,不断的遍历methodList
的过程,通过isa的指向
关系,自己
找不到,找爸爸
,爸爸找不到,找爷爷
,一路向上遍历
查找,直到nil
。因为这个过程是一个耗时过程
,所以放在C/C++
中执行。