美文网首页
十、消息流程—动态方法决议 & 消息转发

十、消息流程—动态方法决议 & 消息转发

作者: 顺7zi燃 | 来源:发表于2020-10-13 16:02 被阅读0次

    主要内容:objc_msgSend快速查找慢速查找,都没有找到方法时,苹果给了挽救的机会
    一、动态方法决议
    1.对象方法
    2.类方法
    二、消息转发
    1.快速消息转发
    2.慢速消息转发

    一、动态方法决议

    动态方法决议:慢速查找流程未找到方法,会给一次机会,重新查询方法
    objc_msgSend流程—慢速查找慢速查找流程未找到方法实现时,会执行一次动态方法决议``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);
    }
    

    流程图


    image.png
    1.对象方法

    对象方法快速查找 -> 慢速查找没有找到的情况下,会走到 resolveInstanceMethod 方法,源码如下:

    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        SEL resolve_sel = @selector(resolveInstanceMethod:);
        // look的是 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消息前,需要查找cls类中是否有该方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
      • 如果没有,则直接返回
      • 如果有,则发送resolveInstanceMethod消息
    • 再次慢速查找实例方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找实例方法

    代码

    • main
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *person = [LGPerson alloc];
            [person ins_say666];
        }
        return 0;
    }
    
    • LGPerson
    @interface LGPerson : NSObject
    - (void) ins_say666;
    - (void)ins_sayMaster;
    + (void) cls_sayHappy;
    @end
    
    @implementation LGPerson
    
    - (void)ins_sayMaster{
        NSLog(@"ins_sayMaster - %s", __func__);
    }
    + (void) cls_sayHappy{
        NSLog(@"cls_sayHappy");
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
        return [super resolveInstanceMethod:sel];
    }
    

    打印结果:

    image.png

    执行到了LGPerson中的resolveInstanceMethod方法,还执行了两次,而且程序还是崩溃了,通过堆栈信息可以看出

    image.png
    • 【第一次动态决议】第一次的“来了”是在查找ins_say666方法时会进入动态方法决议
    • 【第二次动态决议】第二次“来了”是在慢速转发流程中调用了>CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,会再次进入动态决议

    继续修改

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
    
        if (sel == @selector(ins_say666)) {
            //获取ins_sayMaster方法的imp
            IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
            //获取ins_sayMaster的方法签名
            Method sayMMethod = class_getInstanceMethod(self, @selector(ins_sayMaster));
            const char *type  = method_getTypeEncoding(sayMMethod);
            //将sel的实现指向ins_sayMaster
            return class_addMethod(self, sel, imp, type);
        }
        return [super resolveInstanceMethod:sel];
    }
    

    打印结果:


    image.png

    方法重定向后,不再崩溃。

    2.类方法

    对类方法,通过重写resolveClassMethod类方法来解决方法未找到的崩溃问题,即在LGPerson类中重写该方法,并将cls_sayNB类方法的实现指向类方法cls_sayHappy

    + (BOOL)resolveClassMethod:(SEL)sel {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
    
        if (sel == @selector(cls_sayNB)) {
            IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
            Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
            const char *type  = method_getTypeEncoding(sayMHappy);
            return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
        }
        return [super resolveClassMethod:sel];
    }
    

    resolveClassMethod类方法的重写需要注意一点,传入的cls不再是,而是元类,可以通过objc_getMetaClass方法获取类的元类,原因是因为类方法元类中是实例方法

    优化
    上面的这种方式是单独在每个类中重写,有没有更好的?其实通过方法慢速查找流程可以发现查找路径有两条

    • 实例方法:类 -> 父类 -> 根类 -> nil
    • 类方法:元类 -> 根元类 -> 根类 -> nil

    共同点是如果前面没有找到,都会来到 根类即 NSObject中查找。所以我们可以通过 NSObject添加分类的方式来实现统一处理。类方法在查找到元类和根类时,实际查找的也是对象方法,所以 对象方法和类方法都放在resolveInstanceMethod 中处理

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
    
        if (sel == @selector(ins_say666)) {
            IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
            Method sayMMaster = class_getInstanceMethod(self, @selector(ins_sayMaster));
            const char *type  = method_getTypeEncoding(sayMMaster);
            return class_addMethod(self, sel, imp, type);
        } else if (sel == @selector(cls_sayNB)) {
            IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
            Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
            const char *type  = method_getTypeEncoding(sayMHappy);
            return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
        }
        return NO;
    }
    

    上面这种写法还是会有其他的问题,比如系统方法也会被更改,针对这一点,是可以优化的,即我们可以针对自定义类中方法统一方法名的前缀,根据前缀来判断是否是自定义方法,然后统一处理自定义方法,例如可以在崩溃前pop到首页,主要是用于app线上防崩溃的处理,提升用户的体验。也可以收集崩溃信息传到后台,再进行相应处理。

    二、消息转发

    消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
    但我们找遍了源码也没有发现消息转发的相关源码,可以通过instrumentObjcMessageSends 方式打印发送消息日志

    • 通过lookUpImpOrForward -> log_and_fill_cache -> logMessageSend,在logMessageSend源码下方找到instrumentObjcMessageSends的源码实现,所以,在main中调用instrumentObjcMessageSends打印方法调用的日志信息,有以下两点准备工作
      • 1、打开 objcMsgLogEnabled开关,即调用instrumentObjcMessageSends方法时,传入YES
      • 2、在main中通过extern 声明instrumentObjcMessageSends方法
    extern void  instrumentObjcMessageSends(BOOL flag);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *person = [LGPerson alloc];
            instrumentObjcMessageSends(YES);
            [person ins_say666];
            instrumentObjcMessageSends(NO);
        }
        return 0;
    }
    
    • 运行代码,前往 /tmp/msgSends 目录,发现有 msgSends 开头的日志文件,打开日志文件会发现:
      image.png
    1.快速消息转发
    // 快速转发
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //    return [super forwardingTargetForSelector:aSelector];
    //将消息的接收者指定为LGTeacher,在LGTeacher中查找ins_say666的实现
        return [LGTeacher alloc];
    }
    

    打印结果:

    image.png
    2.慢速消息转发

    有快速一般就有慢速,以下是慢速转发流程
    如果forwardingTargetForSelector也没有找到,那就找
    methodSignatureForSelector 方法,使用时会搭配 forwardInvocation, 就会实现慢速转发流程

    // 慢速转发流程
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
        // 返回方法签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%s - %@",__func__,anInvocation);
        anInvocation.target = [LGTeacher alloc];
        [anInvocation invoke];
    }
    

    打印结果

    image.png
    • anInvocation 里存的信息:消息接收者、消息名称、消息签名
    • forwardInvocation方法中,不修改anInvocation也不会崩溃,是因为 GM 认为就是不处理这个方法了,那就谁愿意处理谁处理,把这个方法流出去,像漂流瓶一样
    • 慢速快速区别快速直接指定一个对象就OK了,慢速target可以改,sel 也可以改,可以保存之后想什么时候invoke就什么时候invoke, 可以给一个统一的全局的方法 。慢速转发流程,拥有的 权力更大 , 更加灵活

    总结
    消息流程:快速查找 -> 慢速查找 -> 动态方法决议 -> 消息转发(快速转发 + 慢速转发)

    消息转发流程图.png

    相关文章

      网友评论

          本文标题:十、消息流程—动态方法决议 & 消息转发

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