方法的本质3_消息查找流程


本页所使用的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.");
}
  1. 父类缓存(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;
    }
    }

  2. 父类方法列表找

    // 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. 小结

方法查找的流程,就是在类里查找缓存与方法列表里挖掘的过程。

如上文阐述:

方法查找:缓存查找 —— 的方法列表 —— 父类的方法列表 (递归)—— 动态方法解析 —— 结束

消息转发流程


文章作者: 李佳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李佳 !
评论
 上一篇
【休闲】西湖一瞥 【休闲】西湖一瞥
逛次逛次的西湖今天过来平海路的Apple 直营店给电脑做大保健,顺便看了一眼西湖。 湖边景色依旧宜人,可惜游人寥寥。 期待一切顺利,然后恢复到游人如织的景色。
2020-03-09 李佳
下一篇 
方法的本质2_从objc_msgSend谈起 方法的本质2_从objc_msgSend谈起
方法的本质,就是消息传递… 本页所使用的objc runtime 756.2,来自GITHUB 一、引子:Runtime概念我们都知道,在运行OC代码时,类或者对象在调用方法时会用到runtime,那么,到底什么是运行时呢? 寻找一
2020-02-24 李佳
  目录