OC底层-方法的本质探究之方法的动态决议和消息转发

383 阅读4分钟

写在开始

方法(消息发送)的本质

动态方法决议

动态方法决议是系统给我们的第一个补救的机会。上文我们在慢速查找结束的时候,引入了动态决议的方法resolveMethod_locked。继续向下看源码:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

这里主要区分了实例化方法和类方法的查找:

  • 如果是实例化方法,执行resolveInstanceMethod方法。
  • 如果是类方法,执行resolveClassMethod方法,去nonMetaClass本类中找,如果本类以及本类的父类继承链中没有找到类方法,则会再调用resolveInstanceMethod实例化方法,去元类中查找,这里是因为类方法在元类的中是以对象方法存在的。
  • 补救成功,则消息成功发送
  • 补救失败,进入消息转发流程

resolveInstanceMethod方法探究:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 如果消息接受者没有实现resolveInstanceMethod方法,跳出
  • 如果实现,则objc_msgSendresolveInstanceMethod的方法编号,lookUpImpOrNil递归查找imp,如果有imp,则补救成功。

消息转发

快速转发

forwardingTargetForSelector 官方文档

If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.
  • 文档我们可以看到,如果一个对象实现(或继承)这个方法,并返回一个非nil(和非self)结果,那么返回的对象将用作新的接收者对象,消息分派将继续到这个新对象
  • 当我们只想将消息重定向到另一个对象,并且比常规转发快一个数量级时,此功能很有用。
  • 快速消息转发只能重定向消息的接受者
  • 如果转发的目标是捕获NSInvocation,或者在转发过程中操纵参数或返回值,那么它就没有用了。就会用到慢速转发。

慢速转发

methodSignatureForSelector 官方文档

This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.
  • 文档我们可以看到,如果您的对象维护一个委托或能够处理它没有直接实现的消息,您应该重写此方法以返回适当的方法签名。
  • methodSignatureForSelector必须配合forwardInvocation使用
  • 慢速消息转发不仅可以重新向消息的接受者,也可以重定向消息
  • forwardInvocation方法里面不处理事务也不会崩溃。慢速转发可以对没有实现的方法进行保存,并不马上处理,想什么时候处理都可以。
  • 如果慢速查找也没有找到imp,就会进入第二次动态方法决议

异常抛出

  • 汇编源码分析中,如果慢速查找也没有找到,则会进入到__objc_msgForward_impcache方法。源码如下:
STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

//👇
ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward
  • 继续查找__objc_forward_handler,汇编中没有,去掉一个_在objc中搜索_objc_forward_handler,找到的源码如下
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
  • 这可太熟悉了,日常开发过程中。方法未实现的异常报错原来在这。
  • 这里也有一个细节,前面也提到过,类方法和实例化方法在底层其实都是对象方法。+和-的区别在于是类对象的还是元类对象的。

流程图补充