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

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—消息转发流程)