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
办法的慢速查找流程解析,如有疑问或过错之处,请在评论区留言或私信我,感谢支持~❤