iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
序言
在前一篇iOS底层之Runtime探索(一)中,已经知道了在sel找imp的整个缓存查找过程,这个过程是用汇编实现,是一个快速方法查找流程,今天就来探究一下缓存没有查找到后面的流程。
__objc_msgSend_uncached 方法探究
前面流程是对缓存进行查找imp,如果找不到就走方法__objc_msgSend_uncached方法
在
__objc_msgSend_uncached中,共两条语句MethodTableLookup和TailCallFunctionPointer x17
MethodTableLookup方法解析
MethodTableLookup中的代码逻辑
x0是receiver,x1是sel;mov x2, x16: 将x16也就是cls给x2;mov x3, #3: 将常量值3给x3;- 调用
_lookUpImpOrForward:x0~x3是传的参数;mov x17, x0: 将_lookUpImpOrForward的返回值x0给x17;
通过注释也可以大致看出,调用方法lookUpImpOrForward获取imp,并返回。
TailCallFunctionPointer定义
这里仅仅是对传入的
$0直接调用。
我们可以总结,
__objc_msgSend_uncached方法流程是通过MethodTableLookup获取imp,然后传值到TailCallFunctionPointer定义调用imp。这里的核心就是
MethodTableLookup中的lookUpImpOrForward是怎么获取的imp。
lookUpImpOrForward方法探究
lookUpImpOrForward通过方法命名,可以看出一些端倪,就是查找imp找不到就forward操作。
实例分析
- 对上面定义的
LGPerson添加分类,并在分类中同样实现sayHello方法; - 对
NSObject添加分类,并自定义方法sayNB; - 对
LGTeacher定义方法sayByeBye,但不添加实现;
结果
sayHello调用的是LGPerson的分类方法,why?- 用类对象
LGTeacher调用NSObject的sayNB方法成功,why?- 未实现方法
sayByeBye报错unrecognized selector sent to instance 0x600000008000,why?
lookUpImpOrForward源码分析
源代码添加了中文注释
NEVER_INLINE
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();
// 判断当前类是否初始化,
if (slowpath(!cls->isInitialized())) {
/*
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_NIL = 4,
LOOKUP_NOCACHE = 8,
};
*/
// 如果没有初始化 behavior = LOOKUP_INITIALIZE | LOOKUP_RESOLVER | LOOKUP_NOCACHE
behavior |= LOOKUP_NOCACHE;
}
// 加锁防止多线程访问出现错乱
runtimeLock.lock();
// 检查当前类是否被dyld加载的类
checkIsKnownClass(cls);
// 如果尚未实现给定的类,则实现该类;如果尚未初始化,则初始化该类。
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
runtimeLock.assertLocked();
curClass = cls;
//在我们获取锁后,用于再次查找类缓存的代码,但对于大多数情况,证据表明这在大多数情况下都是失败的,因此会造成时间损失。
//在没有执行某种缓存查找的情况下,唯一调用此函数的代码路径是class_getInstanceMethod()。
for (unsigned attempts = unreasonableClassCount();;) {// 开启循环查找imp
// 继续检查共享缓存是否有方法
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel); // cache_getImp流程获取imp
if (imp) goto done_unlock; // 找到imp走”done_unlock“流程
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 从curClass的method list中查找method.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done; // 查找到imp,跳转done流程
}
// 未从curClass的method list中查找到对应的method,继续往父类查找,直到父类为nil
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp; // 查找不到跳出for循环
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel); // 这里curClass已指向父类,查找父类的缓存中的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)) { // 从父类缓存中查找到imp,跳转到done
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 为找到imp,开始resolveMethod_locked流程,动态方法决议
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
log_and_fill_cache(cls, imp, sel, inst, curClass); // 将获取的imp插入缓存
}
done_unlock:
runtimeLock.unlock(); //runtimeLock 解锁
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
逻辑流程分析
- 判断当前类是否初始化,若未初始化执行
behavior |= LOOKUP_NOCACHE;checkIsKnownClass(cls)检查当前类是否被加载到dyld,类的加载属于懒加载,未加载这里会做加载处理;realizeAndInitializeIfNeeded_locked如果尚未实现给定的类,则实现该类;如果尚未初始化,则初始化该类;- 进入
for循环开始一层层遍历查找imp;curClass->cache.isConstantOptimizedCache检查共享缓存,这个时候,可能别的地方进行缓存了,如果有则直接跳转done_unlock返回imp;- 上面没有缓存,
getMethodNoSuper_nolock从当前类的methodlist中查找method;- 如果找到跳转
done,将找到的imp进行缓存插入,再返回imp;- 如果未找到,
if (slowpath((curClass = curClass->getSuperclass()) == nil))这里将curClass指向父类,并判断如果父类为nil,将imp指向forward_imp,break跳出for循环;- 如果父类存在,通过
cache_getImp检查父类缓存是否有imp,如果imp为forward_imp则跳出循环,然后再检查imp,如果imp有效则跳转done流程;- 查找
for循环结束,没有找到imp,按LOOKUP_RESOLVER判断走动态方法决议流程resolveMethod_locked
归总分析就是,消息查找会沿着继承链一层一层往上查找,直到找到nil,如果有找到则插入缓存并返回imp,如果找不到则imp指向forward_imp也就是_objc_msgForward_impcache。
上面LGTeacher调用NSObject的分类方法sayNB的过程,就是LGTeacher沿着元类的继承链找到了NSObject,并调用sayNB方法。
lookUpImpOrForward流程图
getMethodNoSuper_nolock方法查找解析
在getMethodNoSuper_nolock的方法中,查找method的算法就是findMethodInSortedMethodList方法中的二分查找算法实现的。
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
// method_list_t 数组是通过将sel转为uintptr_t类型的value值进行排序的
auto first = list->begin(); // 起始位置 0
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key; // 目标sel
uint32_t count; // 数组个数
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1); // probe = base + floor(count / 2)
uintptr_t probeValue = (uintptr_t)getName(probe)
if (keyValue == probeValue) { // 目标命中
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 一直循环找最前面的同名方法
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) { // 目标value大于,二分法的中间数
base = probe + 1;
count--;
}
}
return nil;
}
这里的二分算法设计还是挺巧妙的,可以慢慢品玩;其中在keyValue == probeValue的时候,会进入while循环,让probe--,直到找到同名方法中最前面的方法,同名方法就是我们分类中重写的方法,分类中方法会放在前面,所以调用是会优先调用分类中的方法实现。
cache_getImp方法解析
根据之前对
CacheLookup的分析,cache_getImp中MissLabelDynamic传的是LGetImpMissDynamic,因此如果CacheLookup中找不到imp就会进入LGetImpMissDynamic,这里仅仅是返回了0值,所以cache_getImp流程在这里也就断了返回的是0。
forward_imp解析
在取父类(curClass = curClass->getSuperclass()) == nil)的判断中,如果往上没有父类了,即条件成立,那imp会指向_objc_msgForward_impcache类型的forward_imp,并返回执行imp。
_objc_msgForward_impcache中只是对__objc_msgForward进行调用;__objc_msgForward是对__objc_forward_handler相关的方法操作返回值到x17,并通过TailCallFunctionPointer调用x17。
__objc_forward_handler就是函数objc_defaultForwardHandler,这里面就是直接抛出错误unrecognized selector sent to instance
这里的错误信息字符串中,对
+和-的处理是通过元类判断的,底层中没有+号方法或-号方法,都是OC的语法设计。
总结
- 在缓存
cache中没有找到imp,就会通过lookUpImpOrForward开启从当前类到NSObject的方法列表进行层层遍历,这个过程为“慢速查找”过程;- 如果找到了就会返回
imp,并执行;- 如果找不到就会让
imp指向forward_imp并返回,执行forward_imp系统会直接抛出方法未找到的错误;- 在返回
forward_imp之前,还有一步resolveMethod_locked操作,这就是动态方法决议的流程,在下一篇幅展开细讲。
以上是Runtime中对objc_msgSned方法的慢速查找流程解析,如有疑问或错误之处,请在评论区留言或私信我,感谢支持~❤