一,什么时候进入慢速查找
快速查找流程既缓存查找,如果缓存中没有查找到,下面就会进入方法慢速查找流程
二,__objc_msgSend_uncached
在objc源码全局搜索 STATIC_ENTRY __objc_msgSend_uncached
arm64源码内容
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
-
源码分析:核心部分也就
MethodTableLookup和TailCallFunctionPointer x17 -
MethodTableLookup 调用c++代码执行慢速查找流程
-
TailCallFunctionPointer x17拿到上一步的查找结果跳转到x17寄存器
MethodTableLookup
- 全局搜索 arm64源码
.macro MethodTableLookup
SAVE_REGS MSGSEND
// 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_REGS MSGSEND
.endmacro
- 我们来看源码干了什么
- x16 = class 赋值给x2
- 3 赋值给 x3
- 跳转到 _lookUpImpOrForward去查找IMP
- 将查找到的IMP放进x0,并且将x0赋值给x17
TailCallFunctionPointer
//通过慢速查找流程找到了IMP,那么这里就是将跳转至寄存器x17
//以提供后续汇编继续执行
.macro TailCallFunctionPointer
// $0 = function pointer value
// $0表示参数x17
braaz $0
.endmacro
.macro TailCallFunctionPointer
// $0 = function pointer value
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 imp = nil;//创建imp,用于接收通过sel查找的imp
//创建要查找的类,这个类通过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;
class_getInstanceMethod().
/** * 循环查找类对象的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)) {
break;
}
//如果找到了
if (fastpath(imp)) {
//将找到的method插入到缓存中,以便下次查找使用快速缓存查找
goto done;
}
}
//没有找到实现。尝试一次方法解析器。
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;
}
二分法查找流程(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)
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;
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name() == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
二分法查找(findMethodInSortedMethodList)
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; });
}
}
二分法查找真正实现
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;
/**
二分法逻辑在很多地方都通用,能大大缩短查找次数
就比如说 要从1-10000个编号其中有个号码是中奖的我们要去抽奖
比较笨的方式是从1开始抽抽不到就抽2以此类推直到抽中
二分法就是从10000/2 = 5000开始抽,如果抽中直接换奖抽不中会有人提示你你这个号大了还是小了
如果大了 下次抽奖号码就是 0+5000/2 = 2500
如果小了 下次抽奖号码就是 5000+5000/2 = 7500
每次抽奖都是在缩小中奖号码范围,这样的话,我们可以省去很多没必要的次数,快速抽中大奖,真是一件开心的事
*/
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;
}
- 二分法源码抽奖模仿
auto first = 0;
auto base = first;
decltype(first) probe = 0;
uintptr_t keyValue = 8888;
uint32_t count;
uint32_t current = 1;
for (count = 10000; count != 0; count >>= 1) {
probe = base + (count >> 1);
NSLog(@"第%d次抽奖---抽奖号码是%d",current,probe);
if (keyValue == probe) {
NSLog(@"恭喜你中奖了,中奖号码是%d",probe);
break;
}
else{
NSLog(@"没中奖!!再接再厉");
}
if (keyValue > probe) {
base = probe + 1;
count--;
}
current += 1;
}
- 抽奖结果
慢速查找流程 lookUpImpOrForward找到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- >跳出循环