iOS开发 — 消息转发流程

1,516 阅读3分钟

在上篇文章中我们讲到,如果方法查找和动态方法解析都没有找到方法实现,那么就会来到消息转发流程。这次我们就来研究一下消息转发流程。

查找

其实想要找到消息转发流程是件不简单的事,最直接的办法就是看汇编了,不过还好有前辈们探索过,在这里我们也可以借鉴一下。我们在探索方法查找流程的时候有看到一个方法:

log_and_fill_cache

/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled. 
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}

SUPPORT_MESSAGE_LOGGING默认为1,也就是说只要objcMsgLogEnabled为true,就可以打印日志信息了,那么它是在哪里设置的呢?

instrumentObjcMessageSends

我们跟进去再进行全局搜索可以发现这个函数:

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;
}

当调用这个函数时,objcMsgLogEnabled会被赋值,那就可以在我们自己的代码中使用:

LGStudent *student = [LGStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);

运行工程后,前往/tmp/目录,打开最新的msgSends文件:

快速转发流程

文件里有很多方法,其中resolveInstanceMethod我们已经分析过了,是动态方法解析,接下来看一下forwardingTargetForSelector方法,源码如下:

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

从源码中我们看不到任何信息,这个时候只能借助官方文档了:

从官方文档中我们可以得知:

  • 该方法的作用是自己处理不了的方法转发给别的对象处理,也就是重写该方法后返回的对象就是要执行sel的新对象,但是不能返回self,否则会陷入死循环。
  • 如果不实现或者返回nil,就会走到效率较低的forwardInvocation:方法中进行处理。

慢速转发流程

当快速转发流程没有实现,就会来到慢速转发流程,我们从日志打印中寻找到methodSignatureForSelector:方法,查看源码:

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

同样要找官方文档:

  • 这个方法是让我们生成一个NSMethodSignature类型的方法签名并返回。
  • 方法签名里包含返回值类型、参数类型等信息。

光有一个方法签名肯定是不行的,于是进入到forwardInvocation:中,查看源码:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

同样查看文档:

由文档可以得知:

  • 要使用这个方法必须先重写methodSignatureForSelector:方法。
  • 该方法可以指派多个对象来接受这个消息。
  • 使用anInvocation将消息发送到对象。调用将保存结果,运行时系统将提取此结果并将其传递给原始发件人。

如果在这个方法中也没能找到方法实现,那么就会跳到doesNotRecognizeSelector:中报错并且崩溃了:

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

我们可以看到这个方法中的报错信息其实就是我们平常开发中常见的找不到方法的那个错误。

至此,整个消息转发流程也就结束了。