iOS开发 — 消息转发流程

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

查找

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

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,就能够打印日志信息了,那么它是在哪里设置的呢?ui

instrumentObjcMessageSends

咱们跟进去再进行全局搜索能够发现这个函数:this

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会被赋值,那就能够在咱们本身的代码中使用:spa

LGStudent *student = [LGStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
复制代码

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

快速转发流程

文件里有不少方法,其中resolveInstanceMethod咱们已经分析过了,是动态方法解析,接下来看一下forwardingTargetForSelector方法,源码以下:日志

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

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
复制代码

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

从官方文档中咱们能够得知:

  • 该方法的做用是本身处理不了的方法转发给别的对象处理,也就是重写该方法后返回的对象就是要执行sel的新对象,可是不能返回self,不然会陷入死循环。
  • 若是不实现或者返回nil,就会走到效率较低的forwardInvocation:方法中进行处理。

慢速转发流程

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

// 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);
}
复制代码

咱们能够看到这个方法中的报错信息其实就是咱们日常开发中常见的找不到方法的那个错误。

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

相关文章
相关标签/搜索