美文网首页程序员iOS开发技巧
OC-底层原理09—消息转发流程

OC-底层原理09—消息转发流程

作者: 夏天的枫_ | 来源:发表于2020-09-28 00:54 被阅读0次

    iOS--OC底层原理文章汇总

    在前面两章中介绍了方法消息的处理流程,宏观上来说,方法的本质就是对消息的发送,处理消息的过程呢,我们经历了objc_msgSend快速查找、慢速查找。在前面两个环节中,依然在本类、父类继承链、元类继承缓存中找未找到消息,又未采取动态方法决议,对未查找到的方法实现resolveInstancMethod,则就会报错奔溃。这样对于开发者来说是不愿看到的,所以对消息的处理就来到了新的层次,进行消息转发,本章内容将围绕这个展开。

    铺垫

    通过前面分析lookUpImpOrForward,既然是寻找或者转发,那在没寻找到的情况下,它是怎么转发的呢?入口又是在哪?

    • 通过instrumentObjcMessageSends分析方法调用顺序
      换一个思路,既然动态决议后,如果没有对Imp进行操作,就会崩溃,那可以通过该方法检测奔溃时方法的调用情况。lookUpImpOrForward -> log_and_fill_cache -> logMessageSendobjcMsgLogEnabled =YES是进入这个流程的关键.
    //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
        cache_fill(cls, sel, imp, receiver);
    }
    //-------------------------------------------------
    
    bool objcMsgLogEnabled = false;
    static int objcMsgLogFD = -1;
    
    bool logMessageSend(bool isClassMethod,
                        const char *objectsClass,
                        const char *implementingClass,
                        SEL selector)
    {
        char    buf[ 1024 ];
    
        // Create/open the log file
        if (objcMsgLogFD == (-1))
        {
            snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
            objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
            if (objcMsgLogFD < 0) {
                // no log file - disable logging
                objcMsgLogEnabled = false;
                objcMsgLogFD = -1;
                return true;
            }
        }
    
        // Make the log entry
        snprintf(buf, sizeof(buf), "%c %s %s %s\n",
                isClassMethod ? '+' : '-',
                objectsClass,
                implementingClass,
                sel_getName(selector));
    
        objcMsgLogLock.lock();
        write (objcMsgLogFD, buf, strlen(buf));
        objcMsgLogLock.unlock();
    
        // Tell caller to not cache the method
        return false;
    }
    
    // 1: objcMsgLogEnabled 控制开关
    // 2: extern
    
    void instrumentObjcMessageSends(BOOL flag)
    {
        bool enable = flag;
    
        // Shortcut NOP
        if (objcMsgLogEnabled == enable)
            return;
    
        // If enabling, flush all method caches so we get some traces
        if (enable)
            _objc_flush_caches(Nil);
    
        // Sync our log file
        if (objcMsgLogFD != -1)
            fsync (objcMsgLogFD);
    
        objcMsgLogEnabled = enable;
    }
    
    // SUPPORT_MESSAGE_LOGGING
    #endif
    

    /tmp/这个路径就是就是将奔溃日志输出到本地临时缓存了,
    在main.m中调用Book的burnBook的未实现方法,extern:这是一个关键字,是告诉编译器在编译时不要报错,在该类中不存在的方法,请去别的类查找。

    objcMsgLogEnabled = true

    使得objcMsgLogEnabled =true,则就可以查看到在本地生成的一个文件。调用依然会奔溃,但log会输出的。

    msgSends文件路径 文件内容-调用方法顺序
    其中forwardingTargetForSelector就是快速转发方法;慢速转发则是methodSignatureForSelector,其实还搭配forwardInvocation使用。

    消息转发-快速转发

    我们该怎么使用forwardingTargetForSelector 呢?这个时候可以瞄一瞄苹果文档

    forwardingTargetForSelector苹果说明
    如果有无法识别的消息,就将其转发到指定的对象。那我们就可以进行一个操作,再定义一个English的类。如果识别到burnBook被调用,我们就将其转发给另外一个类的方法中,English类中实现了这个方法,就会在English中找寻这个方法。
    // English.m
    -(void)burnBook
    {
        NSLog(@"English burn book");
    }
    

    Book中将方法转发出去,把目标对象返回。

    #import "Book.h"
    #import "English.h"
    
    @implementation Book
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if ([NSStringFromSelector(aSelector) isEqualToString:@"burnBook"]) {
        
            return [English alloc];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    @end
    

    转发到EnglishEnglish实现了该burnBook方法,最终会打印出结果,也不会报错。

    转发消息到其他对象
    这样我们就可以在开发过程中很好的利用这样一点,结合动态方法决议,在需要的地方添加方法,避免应用奔溃,或者利用运行时动态执行一些自定义方法都是很好的方向。

    消息转发-慢速转发

    在快速转发消息之后,就会来到慢速转发(标准转发)消息。找到运行时的方法methodSignatureForSelector

    苹果解释
    返回方法的签名,在苹果的文档中解释了方法的使用场景,也指出在转发消息时要创建NSInvocation对象。
    我们先验证下log里面方法调用顺序,依然是调用burnBook,但是不在forwardingTargetForSelector中处理。再实现方法签名方法,返回父类方法,程序继续奔溃,但是也表示我们的方法按照log中的顺序走下来了。
    快速转发->方法签名
    现在,我们对其进行签名,并实现forwardInvocation
    结果-不崩溃
    经过慢速转发,程序已经不再奔溃,它已经将消息转发出去,自己也不再处理。

    方法签名图


    image.png

    我们可以查看下NSInvocation结构

    NSInvocation定义
    我们可以验证下签名之后的anInvocation中有哪些东西
    签名后的anInvocation
    表明签名后的信息都传递到了- (void)forwardInvocation:(NSInvocation *)anInvocation
    我们可以在将其消息转发给English
    慢速消息转发给English

    消息转发流程

    我们发现和我们之前分析的log文件中方法调用顺序不一致,那么我们可以再做一个操作,实现
    resolveInstanceMethod,打印结果,我们就能发现,与msgSends-10393中的方法执行顺序的一致

    经过一系列的转发,我们可以大致总结到以下一个流程


    消息转发流程

    相关文章

      网友评论

        本文标题:OC-底层原理09—消息转发流程

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