美文网首页
objc_msgSend-消息转发

objc_msgSend-消息转发

作者: 灰溜溜的小王子 | 来源:发表于2020-09-28 17:05 被阅读0次

    在上篇objc_msgSend慢速查找中我们知道了通过慢速查找依然找不到方法实现的情况下程序就会崩溃!再此之前苹果也会给机会防止程序崩溃, 动态方法决议消息转发.

    如果没有实现上述过程怎么样?

    新建类PHFather中一个实例方法和类方法,且在.m中没有实现



    运行结果如下图:
    这个后边分析!

    根据慢速查找发现方法找不到最后都走到__objc_msgForward_impcache

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    ...
     for (unsigned attempts = unreasonableClassCount();;) {
            ...
            if (slowpath((curClass = curClass->superclass) == 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:
        log_and_fill_cache(cls, imp, sel, inst, curClass);
        runtimeLock.unlock();
     done_nolock:
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        return imp;
    }
    
    • 首先赋值const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    • 接下来进行查找如果没有找到方法到最终实现 赋值 imp = forward_imp;
    • 查找objc_msgForward_impcache看内部是怎么实现到
      全局搜索objc_msgForward_impcache可以看到是汇编代码实现,真机arm64架构下查找
    • 汇编实现中查找__objc_forward_handler,并没有找到,在源码中去掉一个下划线进行全局搜索_objc_forward_handler,有如下实现,本质是调用的objc_defaultForwardHandler方法

    多么熟悉到一句话!!!

    • 在慢速查找的流程中,我们了解到,如果快速+慢速没有找到方法实现,动态方法决议也没有找到会怎么处理?
    • 在前面的程序崩溃例子中可以看到找不到方法崩溃控制太打印了unrecognized selector sent ...的提示,同时也打印了堆栈信息:
    • 这里有两种查找崩溃底层调用信息的方法

    就使用消息转发,但是,我们找遍了源码也没有发现消息转发的相关源码,可以通过以下方式来了解,方法调用崩溃前都走了哪些方法

    • 通过instrumentObjcMessageSends方式打印发送消息的日志
    • 通过hopper/IDA反编译
    1.instrumentObjcMessageSends方式

    -lookUpImpOrForward->log_and_fill_cache->logMessageSend中找到决定打印的bool值objcMsgLogEnabled 其实现在此方法后instrumentObjcMessageSends

    • 因为此函数存在.mm中且是C语言函数,调用的时候需要扩展
      • 1.在main文件中通过 extern声明instrumentObjcMessageSends方法
      • 2.打开objcMsgLogEnabled 开关调用instrumentObjcMessageSends方法时,传入YES

        通过logMessageSend源码,了解到消息发送打印信息存储在/tmp/msgSends 目录,如下所示

        运行代码并前往文件夹/tmp/msgSends发现带有msgSends前缀的文件双击打开如图:
    注意:如果打开文件里面没有打印信息

    可见找不到方法执行的时候内部方法的实现情况是:

    • 两次动态方法决议resolveInstanceMethod方法
    • 两次消息快速转发forwardingTargetForSelector方法
    • 两次消息慢速转发methodSignatureForSelector + resolveInstanceMethod
    2.hopper/IDA反编译

    Hopper和IDA是一个可以帮助我们静态分析可视性文件的工具,可以将可执行文件反汇编成伪代码、控制流程图等,因为hopper这个正式版付费,下载链接: https://pan.baidu.com/s/1wSDIsM-OJn_4tvXIfV6_UA 密码: 5vim

    • 通过查看刚开始的崩溃信息可知___forwarding___来自CoreFoundation
      bt打印堆栈信息如下:


      image list打印可执行文件路径找到CoreFoundation的路径 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation

      前往CoreFoundation文件夹找到可执行文件拖入hopper
      搜索__forwarding_prep_0___然后伪代码显示
      双击_forwarding,伪代码实现如下 伪代码-forwardingTargetForSelector
      如果没有响应,跳转至loc_64a67即快速转发没有响应,进入慢速转发流程
    • 如果没有响应methodSignatureForSelector:方法跳转loc_64dd7报错

    • 如果方法签名方法为空跳转loc_64e3c 则直接报错


    • 如果methodSignatureForSelector返回值不为空,则在forwardInvocation方法中对invocation进行处理


      通过以上两种方式分析消息转发方式有两个阶段
    • 快速转发forwardingTargetForSelector

    • 慢速转发

      • methodSignatureForSelector
      • forwardInvocation
    动态方法解析和消息转发

    消息转发的处理主要分为两部分

    • 动态方法决议
    • 消息转发
    1.快速转发
    • 当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法
    • 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程
      如果返回nil,则进入慢速消息转发
    2.慢速转发
    • 执行到methodSignatureForSelector方法
    • 如果返回的方法签名为nil,则直接崩溃报错
    • 如果返回的方法签名不为nil,走到forwardInvocation方法中,对invocation事务进行处理,如果不处理也不会报错
    针对于动态方法决议和消息转发原理做以下解决方案
    1.动态方法决议

    PHFather类的.m中添加resolveInstanceMethod方法并把方法sdd实现指向dynamicMethodIMP

    void dynamicMethodIMP(id self, SEL _cmd) {
        NSLog(@" >> dynamicMethodIMP");
    }
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@" >> Instance resolving %@", NSStringFromSelector(sel));
        if (sel == @selector(sdd)) {
            class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    

    打印如下图


    2.快速转发

    main.m方法中添加类PHAnimal声明并实现sdd方法

    @interface PHAnimal : NSObject
    -(void)sdd;
    @end
    @implementation PHAnimal
    -(void)sdd{
        NSLog(@"%s",__func__);
    }
    @end
    

    PHFather中添加实现方法:

    - (id)forwardingTargetForSelector:(SEL)aSelector{
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
        //     runtime + aSelector + addMethod + imp
            //将消息的接收者指定为PHPerson,在PHPerson中查找sdd的实现
        return [PHPerson alloc];
    }
    
    运行结果

    如果不指定消息接受者,直接调用父类方法如果没有找到还直接报错:


    3.慢速转发
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%s - %@",__func__,anInvocation);
    }
    

    打印结果如下,forwardInvocation方法中不对invocation进行处理,也不会崩溃报错


    forwardInvocation方法中的invocation进行处理,如图

    所以,由上述可知,无论在forwardInvocation方法中是否处理invocation事务,程序都不会崩溃。
    • 那么动态方法决议和转发的数据如下图



      从此处可以看出动态方法决议走了两次:

    • 第一次:正常的动态方法决议resolveInstanceMethod
    • 第二次:在第一次动态方法决议还是没有找到走了快速转发forwardingTargetForSelector慢速转发methodSignatureForSelector,在方法签名之后和处理forwardInvocation事件之前又进行了动态方法决议,那么可以确定的一点儿是第二次动态方法决议的调用肯定是快速转发forwardingTargetForSelector
      为什么?看下篇分析

    相关文章

      网友评论

          本文标题:objc_msgSend-消息转发

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