动态方法解析 & 消息转发

作者: Vency_ | 来源:发表于2019-05-20 14:32 被阅读17次

    消息转发

    由上文 Class 内部结构 可知:
    objc_msgSend 到查找方法实现 lookUpImpOrNil 的时候会先查找 当前类 然后 父类(包括缓存列表)一层一层向上查找, 查找不到的时候就会进入动态方法解析, 那么动态方法解析是怎么实现的?
    且看源码:

    动态方法解析 & 消息转发.png

    1. 动态方法解析

    _class_resolveMethod(cls, sel, inst) :

    void _class_resolveMethod(Class cls, SEL sel, id inst {
        // 1. 不是元类, 则为实例方法调用
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // 2. 类方法动态解析
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                           NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
                // 3. 当步骤 2 没有处理(即 `+resolveClassMethod`返回值为 NO)时, 
                // 才会走到步骤 3.
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    
    实例方法动态解析(类对象)(步骤 1)

    调用没有实现的实例方法时, 会在类对象中查找动态方法解析实现, 即 :
    _class_resolveInstanceMethod(cls, sel, inst) :

    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) {
        // 检查元类是否实现 + resolveInstanceMethod 方法, 没有则直接返回.
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                        NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
            // Resolver not implemented.
            return;
        }
    
        // 发送 + resolveInstanceMethod 消息给当前类(类对象)
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
        // 如果没有处理(NSObject 默认实现返回 NO), 则 imp 为空.
        // 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(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
        // 打印 log
        ...
        }
    }
    
    类方法动态解析(步骤 2)

    元类中查找动态方法解析实现.

    static void _class_resolveClassMethod(Class cls, SEL sel, id inst) {
        // 断言 cls 为元类.
        assert(cls->isMetaClass());
    
        // 检查 cls 是否有实现 resolveClassMethod 方法, 没有则直接返回.
        if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                           NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
            // Resolver not implemented.
            return;
        }
    
        // 给 cls 类对象发送 + resolveClassMethod 消息
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                            SEL_resolveClassMethod, sel);
    
        // 缓存方法的 imp,因为在上面一步已经实现了方法动态解析, 
        // 这里再作查询(包含缓存)操作.
        // Cache the result (good or bad) so the resolver doesn't fire 
        // next time.
        // +resolveClassMethod adds to self->ISA() a.k.a. cls
        IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    
        // 打印 log
        ...
        }
    }
    
    3. 实例方法动态解析(元类对象)(步骤 3) - 重点难理解

    注 : 虽然同步骤 1 的方法相同, 但是当前类cls不一样, 步骤 1 的当前类 cls类对象, 而步骤 3 的当前类 cls元类对象, 相当于在元类中找 +方法(至 NSObject 根类找 -方法) !

    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) {
        // 检查元类是否实现 + resolveInstanceMethod 方法, 没有则直接返回.
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                        NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
            // Resolver not implemented.
            return;
        }
    
        /* 发送 + resolveInstanceMethod 消息给当前类(元类!!!)
           元类中找 '+ 方法' 操作, 仅在查找 NSObject 元类 '+ 方法'
           (查找父类即 NSObject 类对象的 '+ 方法', 即在 NSObject 元类里的 '- 方法')
           时才能找到(NSObject 默认实现返回 NO, 需要重写).
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
        // 如果没有处理(NSObject 默认实现返回 NO), 则 imp 为空.
        // 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(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
        // 打印 log
        ...
        }
    }
    

    2. 消息转发

    在以上动态方法解析失败后, Xcode 便会告诉你找不到方法:

     Terminating app due to uncaught exception 'NSInvalidArgumentException', \
     reason: '+[WXPerson wx_classMethod]: unrecognized selector sent to \
     class 0x100002720'
    *** First throw call stack:
    (
        0   CoreFoundation    0x00007fff3d77ecf9 __exceptionPreprocess + 256
        1   libobjc.A.dylib   0x0000000100357a8f objc_exception_throw + 47
        2   CoreFoundation    0x00007fff3d7f8a56 __CFExceptionProem + 0
        3   CoreFoundation    0x00007fff3d7210ef ___forwarding___ + 1485
        4   CoreFoundation    0x00007fff3d720a98 _CF_forwarding_prep_0 + 120
        5   LGTest            0x0000000100001369 main + 57
        6   libdyld.dylib     0x00007fff69b403d5 start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    发现调用栈中的方法都找不到, 苹果隐藏了消息转发的实现?
    是的, 苹果爸爸觉得这个消息转发的实现非常🐂,于是对该处实现闭源了.
    对此, 先根据已知调用方法进行跟踪查找, 即在方法动态解析失败后, 直接调用 _objc_msgForward_impcache 方法获取 imp.
    那么接下来就查找该方法, 结果发现该方法是由汇编实现:

        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
    

    从中可以看到跳转到 __objcForward 后调用了 __objc_forward_handler :

    // Default forward handler halts the process.
    __attribute__((noreturn)) 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;
    

    终于知道这报错出处了...
    然后就没有然后了...
    但是! 从报错的调用栈中我们仍然可以看到消息转发调用了 CoreFoundation 框架, 于是迫不及待的下载 CoreFoundation 源码进行分析:
    CoreFoundation 下载链接.
    经过一系列查找仍然找不到想要的方法...
    既然我们知道转发是在 CoreFoundation 中, 那么我们从汇编的角度来看下 CoreFoundation 对消息转发到底隐藏了什么:
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework
    用 Hopper 打开 CoreFoundation 可执行文件, 对其进行分析, 由于是在 libobjc 调用了 objc_defaultForwardHandler, 所以在 CoreFoundation 中直接查找 ForwardHandler :

    ForwardHandler.png objc_setForwardHandler.png

    可以看到该 CODE XREF=___CFInitialize+168, 那么直接进入 ___CFInitialize 中查找 objc_setForwardHandler :

    image.png
    可以看到该函数注册了两个回调:
    ___forwarding_prep_0______forwarding_prep_1___
    搜索 ___forwarding_prep_ 关键字发现公有三个相似项:
    ___forwarding_prep_.png
    • ___forwarding_prep_0___

      ___forwarding_prep_0___.png
    • ___forwarding_prep_1___

      ___forwarding_prep_1___.png
    • ___forwarding_prep_b___

      ___forwarding_prep_b___.png
    • ____forwarding___

    int ____forwarding___(int arg0, int arg1) {
        rsi = arg1;
        var_30 = *___stack_chk_guard;
        r13 = arg0;
        rcx = COND_BYTE_SET(NE);
        if (rsi != 0x0) {
                r12 = _objc_msgSend_stret;
        }
        else {
                r12 = _objc_msgSend;
        }
        rbx = *(r13 + rcx * 0x8);
        var_40 = *(r13 + rcx * 0x8 + 0x8);
        r15 = rcx * 0x8;
        if (rbx >= 0x0) goto loc_12c9ba;
    
    loc_12c989:
        rcx = *_objc_debug_taggedpointer_obfuscator;
        rcx = rcx ^ rbx;
        rax = rcx >> 0x3c & 0x7;
        if (rax == 0x7) {
                rax = (rcx >> 0x34 & 0xff) + 0x8;
        }
        if (rax == 0x0) goto loc_12cd4d;
    
    loc_12c9ba:
        var_48 = r15;
        r15 = rsi;
        var_38 = r12;
        r12 = object_getClass(rbx);
        var_50 = class_getName(r12);
        r14 = @selector(forwardingTargetForSelector:);
        if (class_respondsToSelector(r12, r14) == 0x0) goto loc_12ca57;
    
    loc_12c9f2:
        rax = _objc_msgSend(rbx, r14);
        if ((rax == 0x0) || (rax == rbx)) goto loc_12ca57;
    
    loc_12ca0c:
        r12 = var_38;
        r15 = var_48;
        if (rax >= 0x0) goto loc_12ca4a;
    
    loc_12ca19:
        rdx = *_objc_debug_taggedpointer_obfuscator;
        rdx = rdx ^ rax;
        rcx = rdx >> 0x3c & 0x7;
        if (rcx == 0x7) {
                rcx = (rdx >> 0x34 & 0xff) + 0x8;
        }
        if (rcx == 0x0) goto loc_12cd4a;
    
    loc_12ca4a:
        *(0x0 + r15) = rax;
        r13 = 0x0;
        goto loc_12cd7d;
    
    loc_12cd7d:
        if (*___stack_chk_guard == var_30) {
                rax = r13;
        }
        else {
                rax = __stack_chk_fail();
        }
        return rax;
    
    loc_12cd4a:
        rbx = rax;
        goto loc_12cd4d;
    
    loc_12cd4d:
        r14 = _getAtomTarget(rbx);
        *(r13 + r15) = r14;
        ___invoking___(r12, r13, r13, 0x400, 0x0);
        if (*r13 == r14) {
                *r13 = rbx;
        }
        goto loc_12cd7d;
    
    loc_12ca57:
        var_38 = rbx;
        if (strncmp(var_50, "_NSZombie_", 0xa) == 0x0) goto loc_12cdbc;
    
    loc_12ca78:
        rbx = @selector(methodSignatureForSelector:);
        var_48 = r13;
        if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_12ce0d;
    
    loc_12ca96:
        r12 = var_38;
        r14 = _objc_msgSend(r12, rbx);
        if (r14 == 0x0) goto loc_12ce6d;
    
    loc_12cab6:
        rbx = [r14 _frameDescriptor];
        if (((*(int16_t *)(*rbx + 0x22) & 0xffff) >> 0x6 & 0x1) != r15) {
                rax = sel_getName(var_40);
                rsi = " not";
                r8 = "";
                rcx = r8;
                if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) == 0x0) {
                        rcx = rsi;
                }
                rdx = rax;
                if (r15 == 0x0) {
                        r8 = rsi;
                }
                _CFLog(0x4, @"*** NSForwarding: warning: method signature and 
                      compiler disagree on struct-return-edness of '%s'.  
                      Signature thinks it does%s return a struct, 
                      and compiler thinks it does%s.", 
                                            rdx, rcx, r8, r9, stack[2037]);
        }
        var_50 = rbx;
        if (class_respondsToSelector(object_getClass(r12), 
             @selector(_forwardStackInvocation:)) != 0x0) {
                if (*____forwarding___.onceToken != 0xffffffffffffffff) {
                        dispatch_once(____forwarding___.onceToken, ^ { 
                  /* block implemented at ______forwarding____block_invoke */ 
                        });
                }
                r13 = [NSInvocation requiredStackSizeForSignature:r14];
                rdx = *____forwarding___.invClassSize;
                r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
                memset(r12, 0x0, rdx);
                objc_constructInstance(*____forwarding___.invClass, r12);
                var_40 = r13;
                [r12 _initWithMethodSignature:r14 
                                        frame:var_48 
                                       buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) 
                                          size:r13];
                [var_38 _forwardStackInvocation:r12];
                r15 = 0x1;
        }
        else {
                rbx = @selector(forwardInvocation:);
                if (class_respondsToSelector(object_getClass(r12), rbx) != 0x0) {
                        rdi = r12;
                        r12 = [NSInvocation _invocationWithMethodSignature:r14 
                                                                     frame:var_48];
                        _objc_msgSend(rdi, rbx);
                }
                else {
                        r12 = 0x0;
                        _CFLog(0x4, @"*** NSForwarding: warning: 
                                object %p of class '%s' does not implement 
                                forwardInvocation: -- dropping message",
                                0x0, object_getClassName(0x0), r8, r9, stack[2037]);
                }
                var_40 = 0x0;
                r15 = 0x0;
        }
        rax = var_50;
        if (r12->_retainedArgs != 0x0) {
                rax = *rax;
                if (*(int8_t *)(rax + 0x22) < 0x0) {
                        rdx = *(int32_t *)(rax + 0x1c);
                        rsi = *(int8_t *)(rax + 0x20) & 0xff;
                        memmove(*(rsi + var_48 + rdx), 
                                 *(rsi + rdx + r12->_frame), 
                                    *(int32_t *)(*rax + 0x10));
                }
        }
        rbx = [r14 methodReturnType];
        rax = *(int8_t *)rbx;
        if ((rax != 0x76) && (((rax != 0x56) || 
            (*(int8_t *)(rbx + 0x1) != 0x76)))) {
                r13 = r12->_retdata;
                if (r15 != 0x0) {
                        r13 = [[NSData dataWithBytes:r13 length:var_40] bytes];
                        [r12 release];
                        rax = *(int8_t *)rbx;
                }
                if (rax == 0x44) {
                        asm{ fld        tword [r13] };
                }
        }
        else {
                r13 = ____forwarding___.placeholder;
                if (r15 != 0x0) {
                        [r12 release];
                }
        }
        goto loc_12cd7d;
    
    loc_12ce6d:
        r15 = sel_getName(var_40);
        r8 = sel_getUid(r15);
        if (r8 != var_40) {
                _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' 
                       does not match selector known to Objective C runtime 
                       (%p)-- abort", var_40, r15, r8, r9, stack[2037]);
        }
        rbx = @selector(doesNotRecognizeSelector:);
        if (class_respondsToSelector(object_getClass(var_38), rbx) != 0x0) {
                rax = _objc_msgSend(var_38, rbx);
                asm{ ud2 };
        }
        else {
                rax = _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' 
                             does not implement doesNotRecognizeSelector: 
                             -- abort", var_38, object_getClassName(var_38), 
                             r8, r9, stack[2037]);
                asm{ ud2 };
        }
        return rax;
    
    loc_12ce0d:
        rbx = class_getSuperclass(r12);
        r14 = object_getClassName(var_38);
        if (rbx == 0x0) {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' 
                       does not implement methodSignatureForSelector: 
                       -- did you forget to declare the superclass of '%s'?",
                       var_38, r14, object_getClassName(var_38), r9, stack[2037]);
        }
        else {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' 
                       does not implement methodSignatureForSelector: 
                       -- trouble ahead", var_38, r14, r8, r9, stack[2037]);
        }
        goto loc_12ce6d;
    
    loc_12cdbc:
        if (*(int8_t *)___CFOASafe != 0x0) {
                ___CFRecordAllocationEvent();
        }
        rax = _CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p",
                     var_50 + 0xa, sel_getName(var_40), var_38, r9, stack[2037]);
        asm{ ud2 };
        return rax;
    }
    

    Forwarding 内逻辑参考: forwardingforwarding
    以下引用自 00Objective-C 消息发送与转发机制原理(二):
    1、先调用 forwardingTargetForSelector方法获取新的 target 作为 receiver 重新执行 selector,如果返回的内容不合法(为 nil或者跟旧 receiver 一样),那就进入第二步。
    2、调用 methodSignatureForSelector
    获取方法签名后,判断返回类型信息是否正确,再调用 forwardInvocation
    执行 NSInvocation对象,并将结果返回。如果对象没实现methodSignatureForSelector
    方法,进入第三步。
    3、调用 doesNotRecognizeSelector方法。

    image.png

    相关文章

      网友评论

        本文标题:动态方法解析 & 消息转发

        本文链接:https://www.haomeiwen.com/subject/mbzaaqtx.html