美文网首页
消息的动态决议

消息的动态决议

作者: linc_ | 来源:发表于2022-05-22 17:30 被阅读0次

    当lookupImpOrForward函数从cache和methodTable中找不到对应Method,继续向下执行就会来到resolveMethod_locked函数也就是我们常说的动态方法决议

        // No implementation found. Try method resolver once.
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    

    接下来看一下 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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
                resolveInstanceMethod(inst, sel, cls);
            }
        }
    
        // chances are that calling the resolver have populated the cache
        // so attempt using it
        return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
    }
    

    里面有 resolveInstanceMethod 和 resolveClassMethod 2个方法,我们来看一下实现。
    cls->isMetaClass()的作用是判断cls是否是元类,并且对象的实例方法是存在类中的,而类方法是存在元类中的,因此这里:

    如果cls是类,也就是实例方法会调用resolveInstanceMethod方法,
    如果cls是元类,类方法则会调用resolveClassMethod方法,

    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        //首先定义resolveInstanceMethod的方法
        SEL resolve_sel = @selector(resolveInstanceMethod:);
        //先尝试在类的缓存中查找是否有该resolveInstanceMethod方法
        if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
            // Resolver not implemented.
            //没有返回
            return;
        }
        //调用类的resolveInstanceMethod方法
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        //objc_msgSend(消息接收者,方法名,参数),相当于在类中调用resolveInstanceMethod方法,返回true代表处理了该方法,否则就有问题。
        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 = lookUpImpOrNilTryCache(inst, sel, cls);
    
        if (resolved  &&  PrintResolving) {
            //处理错误信息
        }
    }
    

    resolveInstanceMethod函数的入参依次为,实例对象、方法名、类对象。
    resolveInstanceMethod函数内部会看到objc_msgSend函数,可以看到消息的接受者是cls,所以说resolveInstanceMethod是一个类方法。当系统找不到方法,系统就会调用resolveInstanceMethod

    我们在LGPerson类的.m内部实现resolveInstanceMethod函数,并打印,运行:

    @implementation LGPerson
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSLog(@"%s-----%@", __func__ , NSStringFromSelector(sel));
        return [super resolveInstanceMethod:sel];
    }
    
    @end
    

    从打印结果我们可以看到,系统在抛出异常之前会调用resolveInstanceMethod函数,但是会调用2次,它为什么会调用2次呢,我们在文章的最后说。


    image.png

    我们知道objc_msgSend本质就是通过方法名找具体实现的过程。那我们在resolveInstanceMethod函数内部去给该方法添加一个实现看看。我们首先实现一个method1方法,然后获取该方法的imp,添加到本类里面。

    image.png

    这个样运行时就不会抛出异常了,并且正常调用了method1。

    当我们动态的添加了resolveInstanceMethod()方法的时候,系统还是会调用lookUpImpOrNilTryCache()方法,去找IMP。


    image.png

    这个时候这个imp会不会被缓存呢,就是method1这个方法会不会被缓存,我们验证一下。
    通过获取cache里的内容我们找到了test方法,也就是说当方法进行动态决议的时候是会被缓存的.

    下面我们来看一下类方法没有实现时怎么处理。
    刚才我们看到有一个resolveClassMethod, 我们去调用一下类方法test2


    image.png image.png

    打印确实看到调用了resolveClassMethod,并走到自己添加的实现。


    image.png

    再次回到resolveMethod_locked方法中,我们看到元类在调用了resolveClassMethod之后,如果元类中没有imp,那么又再一次调用了resolveInstanceMethod,这是为什么呢?


    image.png

    我们知道实例方法是存在类里边,而类方法是存放在元类中;那如果类方法找不到的时候,我们应该调用元类的 resolveInstanceMethod。但是元类我们无法修改。根据类与元类的继承关系,就会继续往根元类找,最终找到NSObject的resolveInstanceMethod方法。这一整套调用链路会变得非常长,影响系统运行效率;(如果NSObject没有resolveInstanceMethod方法,我们可以通过写分类进行添加)。因此苹果提供resolveClassMethod方法,其实就是为了简化类方法的查找流程,方便在类方法找不到时,直接通过resolveClassMethod来进行类方法决议,提升调用效率;

    image.png

    当类方法在动态决议时,如果没有找到实现他还会调用resolveInstanceMethod()方法,所以还会调用method1()方法
    下面这个例子,在元类中找方法的实现找不到,就要动态决议,会一直找, 变成死循环


    image.png

    在NSObject分类中写上这个方法,不管是实例方法还是类方法找不到的崩溃就不会出现了,有利于程序的稳定,这么写类似aop(面向切面对象:在不修改源代码的情况下,通过运行时来给程序添加统一的功能,用来进行埋点)


    image.png

    消息转发

    如果系统在动态决议阶段没有找到实现,就会进入消息转发阶段。如果类中没有实现resolveInstanceMethod方法,就会调用methodSignatureForSelector方法,我们就看下消息转发流程。

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        省略代码...
     done:
        if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
    #if CONFIG_USE_PREOPT_CACHES
            while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
                cls = cls->cache.preoptFallbackClass();
            }
    #endif
            log_and_fill_cache(cls, imp, sel, inst, curClass);
        }
        省略代码...
    }
    -------
    static void
    log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
    {
    #if SUPPORT_MESSAGE_LOGGING
        if (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    #endif
        cls->cache.insert(sel, imp, receiver);
    }
    

    log_and_fill_cache方法中最主要的就是进行方法缓存cache.insert,但这里还有一个logMessageSend进行消息信息打印的过程,那么我们该如何获取这个打印的消息呢?

    objcMsgLogEnabled我们看到if判断中有对打印控制的参数,说明可以通过设置这个参数来进行打印日志;全局查找,我们可以找到设置objcMsgLogEnabled的函数,也就是通过instrumentObjcMessageSends函数来设置是否打印日志。
    /tmp/msgSends-%d通过查看logMessageSend的源码如下图可以看到,日志输出的路径为/tmp/msgSends-XXX,我们可以在此路径下找到这个日志打印文件。


    image.png
    image.png

    我们调用instrumentObjcMessageSends函数前,需要先声明下该函数,如下:

    extern void instrumentObjcMessageSends(BOOL flag);
    

    然后打开该功能

    instrumentObjcMessageSends(YES);
    

    运行后,就可以在/tmp目录下找到该文件:


    image.png

    可以看到在崩溃之前还调用了两个方法forwardingTargetForSelector和methodSignatureForSelector方法。消息发送在经过动态方法解析仍然没有查找到真正的方法实现,此时动态方法决议进入imp = forward_imp消息转发流程。转发流程分两步快速转发和慢速转发。

    消息的快速转发

    我们在LGPerson类中声明test3方法,不实现,然后定义一个LGBoy类, 在LGBoy类中实现test1,然后在LGPerson类中实现forwardingTargetForSelector:方法,将LGPerson的test3方法转发到LGBoy类中,也就是说,快速转发后的类必须有同名的方法。如下代码:


    image.png
    image.png

    转发的作用在于,如果当前对象无法响应消息,就将它转发给能响应的对象。并且方法缓存在接收转发消息的对象的cache中

    消息的慢速转发

    在快速转发过程中,如果我们不做处理,此时就会进入到methodSignatureForSelector方法, 也就是慢速转发。


    image.png

    消息转发流程总结

    image.png

    相关文章

      网友评论

          本文标题:消息的动态决议

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