写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
以上内容的总结专栏
细枝末节整理
前言
上一篇,我们探索了objc_msgSend
的整个流程。有一个疑问,为什么要用汇编写objc_msgSend
呢?因为,汇编执行的整个流程的速度更接近于机器语言,速度飞快
,而且安全
,缓存机制无非就是要实现效率高一点速度快一点,并且可以更加的动态化
。那么,为什么又会回到c++呢?因为缓存中没有找到,就会进入到慢速查找流程,不断的遍历methodList的一个过程。这会很耗时。最终会bl _lookUpImpOrForward
,这一步其实是从汇编跳转到了c++。那么,今天就 继续 详细看下 慢速查找流程 IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
lookUpImpOrForward
根据sel
查询imp
,具体是怎么查询imp
下面探究下
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//定义消息转发forward_imp //behavior传入的是 3 = LOOKUP_INITIALIZE|LOOKUP_RESOLVER
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
//判断类是否初始化 没有初始化 behavior = LOOKUP_NOCACHE|LOOKUP_INITIALIZE|LOOKUP_RESOLVER
//发送到类的第一个消息通常是+new或+alloc或+self
behavior |= LOOKUP_NOCACHE;
}
//防止并发实现的竞赛。
runtimeLock.lock();
// 检查是否为注册类 被dyld加载 或者是通过合法注册的类
checkIsKnownClass(cls);
//实现类包括实现isa走位中的父类和元类 //初始化类和父类
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
runtimeLock.assertLocked();
curClass = cls;
//之后用于再次查找类缓存的代码 我们采取锁,
//但在绝大多数情况下 证据表明,这在大多数情况下是一种缺失,因此是一种时间损失。
//唯一的代码路径调用这个没有执行一些 类缓存查找是class_getInstanceMethod()。
for (unsigned attempts = unreasonableClassCount();;) {
//判断是否有共享缓存缓存优化,一般是系统的方法比如NSLog,一般的方法不会走
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
/*
再一次查询共享缓存,目的可能在你查询过程中
别的线程可能调用了这个方法共享缓存中有了直接去查询
*/
imp = cache_getImp(curClass, sel);
//如果imp存在即缓存中有 跳转到done_unlock流程
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
// 在curClass类中采用二分查找算法查找methodlist
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) { // 如果找到了sel对应的方法
imp = meth->imp(false); //获取对应的imp
goto done; //跳转到 done 流程
}
// curClass = curClass->getSuperclass() 直到为nil走if里面的流程,不为nil走下面流程
// 没有找到后开始找 curClass的父类是否有实现,一直知道 父类为 nil
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//就是在循环里面没有找到对应的sel的方法,把定义息转发forward_imp赋值给imp
imp = forward_imp;
break;
}
}
// 如果父类的链中存在一个循环 则 停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 先去父类的缓存中昂查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 在超类中发现了forward::条目。
// 停止搜索,但不要缓存;调用方法 这个类的解析器。
// 调用方法 这个类的解析器。
break;
}
if (fastpath(imp)) {
// 在超类中找到该方法。在这个类中缓存它
goto done;
}
}
// 没有实现。尝试一次方法解析器。
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
//动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//将查询到的sel和imp插入到缓存 注意:插入的是当前类的缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
/*
如果 (behavior & LOOKUP_NIL)成立则 behavior != LOOKUP_NIL
且imp == forward_imp 没有查询到直接返回 nil
*/
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
慢速查找流程
- 检查是否为注册类,如果不是的话,直接报错。
- 如果还没有初始化,则实现给定的类,如果还没有初始化,则初始化它。Inst是CLS的一个实例或一个子类,如果不知道则为nil。CLS是要初始化和实现的类。初始化器为true来初始化类,为false来跳过初始化。
cls 查询
- 判断共享缓存中是否存在,因为有可能在查找的过程中,这个方法被调用 而 缓存过,
- 在类中使用 二分查找算法,查找methodlist,如果找到则插入到缓存中去,循环结束
父类的缓存中查询
- 如果父类链中存在循环,则终止查询,跳出循环
- 将superclass赋值给curClass,在父类中进行查找,如果父类中返回 forward_imp 则 跳出遍历,进行消息转发。
- 如果父类中没有找到,则 再次将 superclass赋值给curClass 再在父类中进行查找,知道 curClass == nil, imp = forward_imp 进行消息转发
动态方法决议
- 如果cls 和 父类链中 都没有查找到 imp, 这个时候,系统会给一次机会,判断是否进行过动态方法决议,如果没有则走动态方法决议, 已经执行过了则进行消息转发。
lookUpImpOrForward 流程图
二分查找算法
/***********************************************************************
* search_method_list_inline
**********************************************************************/
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
//第一个method的位置
auto first = list->begin();
auto base = first;
decltype(first) probe; //相当于mid
//把key直接转换成uintptr_t 因为修复过后的method_list_t中的元素是排过序的
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// count = 数组个数 count >>= 1 = count = count >> 1)
// count >> 1 就相当于 (count / 2) 取整
// 1.假如 count = list->count = 8 //2 count = 7 >> 1 = 3
for (count = list->count; count != 0; count >>= 1) {
/*
1. 首地址 + (下标) //地址偏移 中间值 probe = base + 4
2. 中间值 probe = base(首地址)+ 6
*/
probe = base + (count >> 1);
//获取中间的sel的值也是强转后的值
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) { // 如果 目标key == 中间位置的key 匹配成功
//分类覆盖,分类中有相同名字的方法,如果有分类的方法我们就获取分类的方法,多个分类看编译的顺序
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
//返回方法的地址
return &*probe;
}
//如果 keyValue > 中间的位置的值
if (keyValue > probeValue) {
/*
1.base = probe + 1 = 4 + 1 = base(首地址) + 5 向上移 一位
2.base = probe + 1 ;向上移 一位
*/
base = probe + 1;
// 8 -1 = 7 因为比过一次没中 然后循环
count--;//查询完没找到返回nil
}
}
return nil;
}
以上代码就是二分查找法的代码,真的很🐂🍺,那么,代码看的不够直观,我们尝试用几张图来阐述一下上面的过程,让大家一起来领略下 二分查找法的魅力
。
总结
方法的查找流程实际是一个很复杂的流程。现在我们也只是探讨了快速汇编超找流程和慢速超找流程,后面还有动态方法决议,以及消息转发流程,大家加油!