13.动态决议与消息转发

前言

在上一文中我们有探究到objc_msgSend相关的一些底层原理。

  • 我们发现在慢速查找的过程中有这样

    • forward_imp 消息转发

    • resolveMethod_locked 动态决议

  • 本文就将对这两个地方做深入探究

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    
    ...

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
            ...
        } else {
            ...
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        ...
    }

    // No implementation found. Try method resolver once.
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

    ...

 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

先附上一张流程图

  • 仅从源码分析会很困难,结合代码调试各种情况,和这张流程图可以更好的理解整个流程的细节

  • 我自己也反复调试了很多次才理的差不多,以前知道这些东西,但没有深入底层去理解细节

  • 这张图也改了好几次

动态决议与消息转发

一、 _objc_msgForward_impcache

通过在源码objc-msg-arm64.s中搜索我们一步步找到核心代码

__objc_msgForward_impcache

__objc_msgForward

__objc_forward_handler

  • 我们并没有找到 __objc_forward_handler

    • 怀疑不是汇编代码,去掉_试试

objc_defaultForwardHandler

找到了下面的代码。

二、 resolveMethod_locked

三、实例方法动态决议

这里声明一个实例方法,但不做实现。触发调用

会见到我们常见的一个崩溃信息:

3.1 动态添加Method

重写 + (BOOL)resolveInstanceMethod:(SEL)sel 添加method

输出:

3.2 如果再次调用,还会不会进行动态决议呢?

理论分析:第一次添加了Method后,会同步插入到方法缓存中,所以第二次执行就不会再次添加。

实际运行结果也验证了刚才的分析。

3.3 源码解析

四、类方法动态决议

4.1 动态添加Method

  • 如果你写成上面的样子,你会发现依旧会崩溃。根本没生效。

  • 为什么呢?

  • 如果你不知道为什么,你需要首先搞明白 类方法存在哪里

正确的实现方式:

输出:

4.2 类方法动态决议的特殊处理

在源码中及流程图中可以发现,在 resolveClassMethod 之后如果缓存中还没有对应的Method的话就会再去调用 resolveInstanceMethod

可见苹果工程师也是操碎了心啊,给了好多机会来让我们不要崩溃

五、消息转发

lookUpImpOrForward 中我们找到 _objc_msgForward_impcache

5.1 寻找切入点

发现其实现在 objc-msg-arm64.s

__objc_msgForward_impcache

__objc_msgForward

_objc_forward_handler

  • 我们有进一步找到了 _objc_forward_handler

    • 拥有一个默认的实现: objc_defaultForwardHandler

    • 以及一个set方法:objc_setForwardHandler

进一步搜索 setForwardHandler 并未发现相关调用,推测调用处不在OC源码中。

消息转发01

分析Log

发现调用了 CoreFoundation 中的 ___forwarding___ 方法。

那么怎么去进一步分析呢?

5.2 汇编分析 CoreFoundation`forwarding:

forwardingTargetForSelector:

0x7fff203f741b <+184>: mov r14, qword ptr [rip + 0x604ed906] ; "forwardingTargetForSelector:"

通过这条指令我们发现一个 forwardingTargetForSelector:

我们找下源码,根据命名推测这个方法用来是转发消息给某个对象的。

我们下个断点:

发现断点确实走到了这里,输出堆栈信息:

接下来我们试着在这里返回一个对象来接收消息看看效果。

运行后执行正常,输出了 -[RYSubModel noIMP]

5.3 转发对象为空的情况

当不实现 forwardingTargetForSelector 的时候,我们发现,经过一次动态决议默认返回 nil 后,又一次进入了 动态决议

  • behavior == 2 == LOOKUP_RESOLVER

    • 满足动态决议的条件,再次进行动态决议

    • behavior ^= LOOKUP_RESOLVER

      • behavior变成0

六、总结

这部分的理解还是需要结合调试,单纯看源码很难理清楚,有太多嵌套调用。

希望我的流程图能帮助理解,有不对的地方也欢迎指出。

Last updated

Was this helpful?