iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
序言
在前一篇iOS底层之Runtime探索(一)中,已经知道了在sel找imp的整个缓存查找进程,这个进程是用汇编完结,是一个快速办法查找流程,今天就来探求一下缓存没有查找到后边的流程。
__objc_msgSend_uncached 办法探求
前面流程是对缓存进行查找imp,假如找不到就走办法__objc_msgSend_uncached办法

__objc_msgSend_uncached中,共两条句子MethodTableLookup和TailCallFunctionPointer x17
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界说
咱们能够总结,
__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办法的慢速查找流程解析,如有疑问或过错之处,请在评论区留言或私信我,感谢支持~❤





