本页所使用的objc runtime 756.2,来自GITHUB
1. 概念
在前文中,已经总结了方法查找的流程,今天从代码层面上继续阐述。
isa 的指向图如下所示:
2. 方法查找流程
2.1 从业务代码分析
配置代码环境:,先从子类父类方法查找说起,先创建几个类,有:
父类 Person 继承自NSObject,有方法
talk
子类 Student 继承自 Person,有方法
sayEnglish
扩展 NSObject + sayGerman,有方法
sayGerman
具体代码如下所示
主业务代码图:
由上图可以看出,我们生成的子类Student
对象,既可以执行自己的对象方法sayEnglish
,也可以执行父类方法talk
,当需要执行的方法——子类和父类都没有之后,也可以执行根类扩展方法sayGerman
。
查找逻辑图可以初步理解为:子类——> 父类——> 父类的父类 ——>根元类(NSObject)
类方法也是类似的,就不赘述
2.2 源码分析
2.2.1 class_lookupMethodAndLoadCache3 - 慢速查找开启
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
原来上文留下的class_lookupMethodAndLoadCache3 方法,最终执行的方法是lookUpImpOrForward,继续探究一下!
lookUpImpOrForward - 查找Imp 或转发消息
乐观检查是否有缓存
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
查找类是否缓存过
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
这里通过runtimeLock
锁住该部分内存,进行查找,执行了isKnownClass
的方法,具体实现在这里
/***********************************************************************
* isKnownClass
* Return true if the class is known to the runtime (located within the
* shared cache, within the data segment of a loaded image, or has been
* allocated with obj_allocateClassPair).
**********************************************************************/
static bool isKnownClass(Class cls) {
// The order of conditionals here is important for speed. We want to
// put the most common cases first, but also the fastest cases
// first. Checking the shared region is both fast and common.
// Checking allocatedClasses is fast, but may not be common,
// depending on what the program is doing. Checking if data segments
// contain the address is slow, so do it last.
return (sharedRegionContains(cls) ||
NXHashMember(allocatedClasses, cls) ||
dataSegmentsContain(cls));
}
在类的缓存(cache_t)找
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
在类的方法列表里寻找
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
在父类的缓存和方法列表寻找
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
父类缓存(Cache)找
// Superclass cache. imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } }
父类方法列表找
// Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; }
有可能内存覆盖,再给一次查找
这里的核心方法是
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
父类也没有——查找失败——报错
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
但是这个_objc_msgForward_impcache
的实现可不好找,在源码里只看到了这些:
#if !OBJC_OLD_DISPATCH_PROTOTYPES
extern void _objc_msgForward_impcache(void);
最终通过搜索查找_objc_msgForward_impcache,在汇编源码找到了类似的代码
得到这个叫做__objc_forward_handler 的代码块,搜索得知它继承自objc_defaultForwardHandler,
继续查找,得到最终的源码如下:
OH MY GOD! 原来这就是传说中——让程序员捶胸顿足的方法查找失败的代码
至此,方法查找的流程已经捋完。
3. 小结
方法查找的流程,就是在类里查找缓存与方法列表里挖掘的过程。
如上文阐述:
方法查找:缓存查找 —— 类的方法列表 —— 父类的方法列表 (递归)—— 动态方法解析 —— 结束