美文网首页iOS底层进阶iOS底层
探索Runtime动态方法解析与消息转发流程

探索Runtime动态方法解析与消息转发流程

作者: 聪莞 | 来源:发表于2019-03-02 17:43 被阅读6546次

    探索objc_msgSend函数的实现流程一文中,我们最后分析到了 lookUpImpOrForward 函数,该函数会从自己和父类的method_list中遍历寻找objc_msgSend中的SEL所需要的IMP,如果遍历结束后仍然未找到对应的IMP,就会进入动态方法解析流程,动态方法解析还未找见的话,就会进入消息转发流程,今天我们还是从runtime源码来着手分析,动态方法解析和消息转发的流程。

        // No implementation found. Try method resolver once.
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlock();
            _class_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;        //消息转发
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlock();
    
        return imp;
    

    动态方法解析

    我们开始分析动态方法解析前,先要了解一些概念,先来查看一个类的结构:

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    再来了解一下isa:isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。所以我们可以把类方法看做是其isa指向的元类的实例方法。看一张关系图(isa走位图):


    image.png

    举个例子说明一下:假设有个Person类继承自NSObject,再有一个Student类继承自Person,那么Student就是图中的SubClass,Person就是图中的SuperClass,NSObject便是其中的RootClass。
    需要弄清的有两点:

    1. 所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。
    2. 根元类的父类是根类。
      3.NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,所以如下调用是不会报错的
    @interface NSObject (Add)
    +(void)hello;
    @end
    @implementation NSObject (Add)
    -(void)hello{
        NSLog(@"hello...");
    }
    @end
    
    //然后调用
    [NSObject hello];
    
    

    接下来我们进入正题,开始分析动听方法解析(_class_resolveMethod) 的执行流程:

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    { 
        if (! cls->isMetaClass()) {      //如果cls不是一个元类,说明调用的是一个实例方法
            //此时 cls表示的是类,而inst代表的是类的实例
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            //此时 cls表示的是元类,而inst代表的是类
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    
    1. 实例方法解析
    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
         //cls->ISA() 此时指向的是元类,判断元类是否实现了resolveInstanceMethod方法(相当于该类判断是否有resolveInstanceMethod的类方法)
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // Resolver not implemented.
            return;
        }
    
        //如果找到了 就重启消息发送流程
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                                 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    
        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));
            }
        }
    }
    
    IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
    {
        IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
        if (imp == _objc_msgForward_impcache) return nil;
        else return imp;
    }
    
    
    可以看到,又进入了 lookUpImpOrForward 方法去查找IMP,我们知道lookUpImpOrForward方法里如果没有递归查找到IMP就又会触发动态方法解析和消息转发,这样不是陷入死循环了吗?其实并不是这样,我们按执行顺序开始走一遍,先去元类里查找,然后再去元类的父类也就是根源类查找,都没有找到,再去根源类的父类也就是NSObject查找,我们查看NSObject的源码: image.png

    发现NSObject是实现了resolveInstanceMethod这个方法,只是返回了NO。系统就是通过这样的方式来避免产生死递归。
    看源码可以看到,如果找到了resolveInstanceMethod方法,系统就会重新执行一遍 objc_msgSend 的流程,去回调这个方法

    1. 类方法解析

    看源码

    _class_resolveClassMethod(cls, sel, inst);
      if (!lookUpImpOrNil(cls, sel, inst, 
             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    

    1、_class_resolveClassMethod:开始从元类里查找resolveClassMethod方法,一直递归去查找,这里如果找到了,依然会重启消息发送方法

    static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
    {
    //此时的cls是元类,inst是类
        assert(cls->isMetaClass());
    
        if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                            SEL_resolveClassMethod, sel);
    

    注意:跟实例方法不同的是:类方法重启objc_msgSend时传入的参数是_class_getNonMetaClass(cls, inst),而实例方法重启时传的参数是cls(类对象),下面我们来分析一下_class_getNonMetaClass的实现:

    Class _class_getNonMetaClass(Class cls, id obj)
    {
        mutex_locker_t lock(runtimeLock);
        cls = getNonMetaClass(cls, obj);
        assert(cls->isRealized());
        return cls;
    }
    static Class getNonMetaClass(Class metacls, id inst)
    {
        static int total, named, secondary, sharedcache;
        runtimeLock.assertLocked();
    
        realizeClass(metacls);
    
        total++;
    
        //metacls是元类  inst是类
        // 如果metacls不是元类了,说明此时传进来的inst类是NSObject,就直接返回元类
        if (!metacls->isMetaClass()) return metacls;
    
        // metacls really is a metaclass
    
        // special case for root metaclass
        // where inst == inst->ISA() == metacls is possible
        //如果元类的isa指向了自己,说明此时元类是根元类  ,返回它的父类 即NSObject
        if (metacls->ISA() == metacls) {
            Class cls = metacls->superclass;
            assert(cls->isRealized());
            assert(!cls->isMetaClass());
            assert(cls->ISA() == metacls);
            if (cls->ISA() == metacls) return cls;
        }
    
        //一般情况
        if (inst) {
            Class cls = (Class)inst;
            realizeClass(cls);
            // cls may be a subclass - find the real class for metacls
            while (cls  &&  cls->ISA() != metacls) {
    //这里主要判断isa是否被篡改 — 异常判断
                cls = cls->superclass;
                realizeClass(cls);
            }
            if (cls) {
                assert(!cls->isMetaClass());
                assert(cls->ISA() == metacls);
    //直接返回cls 类对象
                return cls;
            }
    #if DEBUG
            _objc_fatal("cls is not an instance of metacls");
    #else
            // release build: be forgiving and fall through to slow lookups
    #endif
        }
    
        // try name lookup
        {
            Class cls = getClass(metacls->mangledName());
            if (cls->ISA() == metacls) {
                named++;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: %d/%d (%g%%) "
                                 "successful by-name metaclass lookups",
                                 named, total, named*100.0/total);
                }
    
                realizeClass(cls);
                return cls;
            }
        }
    
        // try secondary table
        {
            Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
            if (cls) {
                secondary++;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: %d/%d (%g%%) "
                                 "successful secondary metaclass lookups",
                                 secondary, total, secondary*100.0/total);
                }
    
                assert(cls->ISA() == metacls);            
                realizeClass(cls);
                return cls;
            }
        }
    
        // try any duplicates in the dyld shared cache
        {
            Class cls = nil;
    
            int count;
            Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count);
            if (classes) {
                for (int i = 0; i < count; i++) {
                    if (classes[i]->ISA() == metacls) {
                        cls = classes[i];
                        break;
                    }
                }
                free(classes);
            }
    
            if (cls) {
                sharedcache++;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: %d/%d (%g%%) "
                                 "successful shared cache metaclass lookups",
                                 sharedcache, total, sharedcache*100.0/total);
                }
    
                realizeClass(cls);
                return cls;
            }
        }
    
        _objc_fatal("no class for metaclass %p", (void*)metacls);
    }
    

    可以看到,_class_getNonMetaClass的正常流程还是返回了类对象,为什么要返回类对象呢,我们正常的思路是谁出问题,在谁那里修复(应该在元类里取修复),但是类方法是在元类中的,元类是一个虚拟的类,没办法进行书写,所以苹果就设计了这么一个方式,直接返回类,在类里面去实现resolveClassMethod

    2、但是我们看类方法的_class_resolveMethod里发现,_class_resolveClassMethod执行完了如果没有找到的话,还有一步操作_class_resolveInstanceMethod(与上面实例方法步骤一致),该方法会继续从元类里寻找resolveInstanceMethod方法,直到NSObject,这也证明了我们前面的NSObject (Add)类的代码运行不会报错。

    综上所述,我们可以在NSObject扩展里实现resolveInstanceMethod方法,就可以拦截所有的未实现方法。

    消息转发

    如果动态方法解析没有进行任何处理,那么就会进入消息转发流程:

    imp = (IMP)_objc_msgForward_impcache;
    
        STATIC_ENTRY __objc_msgForward_impcache
        // Method cache version
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
    
        beq __objc_msgForward
        b   __objc_msgForward_stret
        
        END_ENTRY __objc_msgForward_impcache
    

    发现此处被苹果闭源了,那么就没有办法了吗???
    我们写一个测试代码

    2019-03-02 17:15:45.456611+0800 CrashDemo[42591:4806004] -[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000
    2019-03-02 17:15:45.464384+0800 CrashDemo[42591:4806004] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x00000001096301bb __exceptionPreprocess + 331
        1   libobjc.A.dylib                     0x00000001086f3735 objc_exception_throw + 48
        2   CoreFoundation                      0x000000010964ef44 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
        3   UIKitCore                           0x000000010bfeab4a -[UIResponder doesNotRecognizeSelector:] + 287
        4   CoreFoundation                      0x0000000109634ed6 ___forwarding___ + 1446
        5   CoreFoundation                      0x0000000109636da8 _CF_forwarding_prep_0 + 120
        6   CrashDemo                           0x0000000107dd67f0 main + 80
        7   libdyld.dylib                       0x000000010aac9575 start + 1
        8   ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    查看堆栈信息可以看出是在CoreFoundation下执行了_forwarding_prep_0、forwarding等方法,所以我们来尝试反编译CoreFoundation的可执行文件来尝试一下是否可以找到一些信息:

    image.png
    还真的被我们找到了_forwarding_prep_0这个信息,点进去:
    int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
        *(rsp + 0xa0) = zero_extend_64(xmm7);
        *(rsp + 0x90) = zero_extend_64(xmm6);
        *(rsp + 0x80) = zero_extend_64(xmm5);
        *(rsp + 0x70) = zero_extend_64(xmm4);
        *(rsp + 0x60) = zero_extend_64(xmm3);
        *(rsp + 0x50) = zero_extend_64(xmm2);
        *(rsp + 0x40) = zero_extend_64(xmm1);
        *(rsp + 0x30) = zero_extend_64(xmm0);
        stack[2021] = arg0;
        rax = ____forwarding___(rsp, 0x0);
        if (rax != 0x0) {
                rax = *rax;
        }
        else {
                rax = objc_msgSend(stack[2021], stack[2021]);
        }
        return rax;
    }
    再点进forwarding:
    int ____forwarding___(int arg0, int arg1) {
        rsi = arg1;
        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_12c9ba:
        var_48 = r15;
        r15 = rsi;
        var_38 = r12;
        r12 = object_getClass(rbx);
        var_50 = class_getName(r12);
        if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_12ca57;
    

    会发现这么一段,if (class_respondsToSelector(r12,@selector(forwardingTargetForSelector:)) == 0x0)
    如果forwardingTargetForSelector方法实现了的话,会继续进行转发,如果没有实现的话,会发现还有这么一段代码:

    loc_12ca78:
        var_48 = r13;
        if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_12ce0d;
    
    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_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]);
        }
        if (class_respondsToSelector(object_getClass(var_38), @selector(doesNotRecognizeSelector:)) != 0x0) {
                [var_38];
                asm { ud2 };
                rax = loc_12ced8(rdi, rsi);
        }
        else {
                _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 };
                rax = loc_12ceff();
        }
        return rax;
    
    loc_12ca96:
        r12 = var_38;
        r14 = [r12 methodSignatureForSelector:var_40];
        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);
                r8 = "";
                rcx = r8;
                if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) == 0x0) {
                        rcx = " not";
                }
                rdx = rax;
                if (r15 == 0x0) {
                        r8 = " not";
                }
                _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 {
                if (class_respondsToSelector(object_getClass(r12), @selector(forwardInvocation:)) != 0x0) {
                        rax = [NSInvocation _invocationWithMethodSignature:r14 frame:var_48];
                        rdi = r12;
                        r12 = rax;
                        [rdi forwardInvocation:r12];
                }
                else {
                        rcx = object_getClassName(r12);
                        rdx = r12;
                        r12 = 0x0;
                        _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", rdx, rcx, r8, r9, stack[2037]);
                }
                var_40 = 0x0;
                r15 = 0x0;
        }
        rax = var_50;
    

    可以猜出来,r14即是methodSignatureForSelector:获取到的方法前面,然后接下来会判断并最后执行forwardInvocation进行消息重定向。

    至此,iOS Runtime动态方法解析和消息转发流程已执行完成。最后一步消息转发是闭源的,过程比较模糊,只作为一个了解,我们也可以通过

            instrumentObjcMessageSends(YES);
            test code
            instrumentObjcMessageSends(NO);
    

    去打印runtime消息执行并生成文件,文件目录在/private/tmp/ 文件夹,找到最新的 msgSends-xxxx文件同样可以看到消息转发的执行流程。

    相关文章

      网友评论

        本文标题:探索Runtime动态方法解析与消息转发流程

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