本文由快学吧个人写作,以任何形式转载请表明原文出处
一、资料准备
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、思路
- 如果方法没有在缓存中存在,那么objc_msgSend是如何找到方法的实现的。
- 通过汇编找到对应的代码。
- 查看源码的实现
三、创建代码
- 在818.2中创建一个类(我创建的是JDPerson),继承于NSObject。并且定义一个实例方法。然后在main.m中创建实例,并调用这个实例方法。并在调用实例方法这一行打断点。
- 打开汇编
- 运行代码
四、如何找到另一种方法查找流程
- 运行代码后,会进入汇编,在汇编中找到
objc_msgSend那行,并打上断点,然后跳到objc_msgSend的断点处。
- 进入objc_msgSend。
- 在汇编的最后面会发现
_objc_msgSend_uncached。在这里打上断点,然后执行到这里。然后按住control再点stepInto。
- 发现调用的方法是
lookUpImpOrForward
- 在818.2中找到
lookUpImpOrForward的源码实现。
lookUpImpOrForward这个函数的最后的返回值是imp,证明这是我们要找的。
五、lookUpImpOrForward的源码解析
这里只挑源码复制在下面,官方的注释看情况,对探索有用的会放出来,解析都在下面的备注里 :
1. 源码及解析
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//定义一个forward_imp,并给它赋值为(IMP)_objc_msgForward_impcache
//这个不是没用的,这个后面会有用
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
//判断类是否已经初始化过
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
//加锁
runtimeLock.lock();
//检查调用方法的这个类是否是已知或者说已经加载
checkIsKnownClass(cls);
//懒加载类、加载类、并且递归加载父类、元类,并保证它们是已经初始化的
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
runtimeLock.assertLocked();
//将cls赋值给curClass,方便下面的操作,curClass会变成cls的父类,甚至父类的父类,直到NSObject的父类为nil为止
curClass = cls;
//死循环,只有goto和break才能跳出
for (unsigned attempts = unreasonableClassCount();;) {
//这里的if永远进不去,原因是isConstantOptimizedCache这个函数。
//下面会有isConstantOptimizedCache的源码图
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES //宏定义,arm64是1
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
// 获取cls的methods列表中的sel的imp
Method meth = getMethodNoSuper_nolock(curClass, sel);
// 如果能找到imp,直接去到done流程
if (meth) {
imp = meth->imp(false);
goto done;
}
//如果curClass的methods列表中没有sel的imp,
//则令curClass变成自己的父类,并且判断父类是否为nil。
//如果curClass的父类为nil,则证明此时的curClass就是NSObject
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
//curClass为NSObject的时候会进入if
//cls的整条继承链都找不到sel的imp实现,则给imp赋值为forward_imp,并且跳出for循环
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
//如果在父类继承链中存在循环,则停止for循环
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 在父类的缓存中找这个方法的imp是否缓存过
imp = cache_getImp(curClass, sel);
//如果imp已经变成了forward_imp,也就是当前这个循环中,curClass已经是NSObject了,则跳出for循环
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;
}
//如果父类的缓存中有这个方法的imp,去到 done 流程
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 找完NSObject的方法列表和缓存都找不到imp,则尝试一次动态决议
// behavior的值会控制动态决议只进行一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//如果找到了imp,则将imp和sel存入到cls,也就是调用类的缓存中
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//将imp和sel存到类的缓存中
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
//解锁
done_unlock:
runtimeLock.unlock();
// 找不到imp,直接返回nil
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
//返回imp。
return imp;
}
2. isConstantOptimizedCache源码
-
非真机环境的情况下,
isConstantOptimizedCache的方法实现就在定义的后面,就是{ return false; },所以非真机的isConstantOptimizedCache是false。 -
如果是真机的环境下,需要全局搜索
isConstantOptimizedCache的代码实现,找到如下 :
- 在
lookUpImpOrForward中,isConstantOptimizedCache的strict参数都是赋值为true的,所以isConstantOptimizedCache的返回值都是false。
六、lookUpImpOrForward流程梳理
(1). 对调用方法的类进行判断是否合规,不合规的将它变成合规的。然后将cls交给curClass进行操作。
(2). 定义一个循环。
(3). 查找类的方法列表中是否存有sel的imp。如果有imp,跳出循环,将sel和imp存到类的缓存中,并返回imp。
(4). 如果类的方法列表中没有imp,则查找父类的缓存中,是否有sel的imp存在。如果父类的缓存中有imp,跳出循环,将sel和imp存到类的缓存中,并返回imp。
(5). 如果父类的缓存中也没有sel的imp,则继续循环,查找父类的方法列表中是否有imp,查找父类的父类的缓存中是否有imp。循环到已经查找完NSObject的方法列表和缓存中是否有imp。
如果循环途中找到了imp,则会跳出循环,将sel和imp存储到类的缓存中,并返回imp。
如果沿着继承链,找到NSObject的方法列表和缓存中都没有imp,则给imp赋值为_objc_msgForward_impcache。
(6). 如果找到NSObject都找不到sel的imp,那么执行一次动态决议。
(7). 如果动态决议找到了,则将imp和sel存到类的缓存中。
(8). 如果动态决议找不到,则直接返回nil。