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);
}
複製代碼

咱們能夠看到這個方法中的報錯信息其實就是咱們日常開發中常見的找不到方法的那個錯誤。

至此,整個消息轉發流程也就結束了。

相關文章
相關標籤/搜索