iOS底層原理探索-08- Runtime之消息轉發

《目錄-iOS & OpenGL & OpenGL ES & Metal》markdown

runtime的消息查找階段已經探索完了,當都不知足條件,會進入消息轉發階段app

前言

runtime的消息轉發分爲3步:post

  • 動態方法解析
    • 對象方法解析
    • 類方法解析
  • 快速轉發
  • 慢速轉發
    • 方法簽名
    • 消息轉發

1、動態方法解析

根據上篇文章,咱們看了lookUpImpOrForward這個方法,在最後都不知足的狀況下,會調用resolveMethod_locked這個方法ui

一、resolveMethod_locked

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

複製代碼
  • 判斷
    • cls若是不是元類,說明當前是對象方法,調用resolveInstanceMethodspa

    • cls若是是元類,說明當前是類方法,調用resolveClassMethod。走完若是沒找到,還會走一遍resolveInstanceMethod方法3d

  • 最後再調用lookUpImpOrForward方法,返回imp

二、對象方法解析resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
複製代碼
  • 容錯判斷if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) ,是爲了看當前類以及父類、元類等有沒有實現resolveInstanceMethod:,若是都沒有就不必繼續往下走了。(NSObject裏面是默認有這個方法的)日誌

  • 若是實現了,就經過objc_msgSend,向當前cls發送消息,也就是調用resolveInstanceMethod:這個已經實現的方法,在這個方法裏,咱們已經手動給sel添加了一個impcode

  • 而後再經過lookUpImpOrNil檢查一遍,拿到咱們添加的imporm

  • 最後返回到lookUpImpOrForward方法,從新循環去找,最終返回imp對象

二、類方法解析resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
複製代碼
  • 和上面的容錯同樣

  • 若是實現了,就經過objc_msgSend,向當前cls的元類 ->發送消息,調用resolveClassMethod方法,給sel添加imp,跟上面流程同樣

  • 特別提醒:若是沒有實現,還會走一次 對象方法解析 resolveInstanceMethod,是由於有一種特殊狀況,就是元類的父類是根元類,根元類的父類是NSObject,因此要在走一次這個方法,看NSObject裏面有沒有實現。

    • 其實也能夠說,全部沒有實現的對象方法和類方法,都會跑進NSObject裏面的 resolveInstanceMethod方法,但並不意味着 全部的防崩潰方法都要寫在這裏!緣由:
    • 耦合度過高
    • 子類中已經重寫了這個方法
    • 一些特殊方法直接被跳轉,體驗就會不好

代碼示例:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(sel);
    
    if ([name isEqual:@"sayHello"]) {
    
        //若是是調用sayHello方法,就返回一個sayHappy的IMP
        IMP newIMP = class_getMethodImplementation(self, @selector(sayHappy));
        
        Method newMethod = class_getInstanceMethod(self, @selector(sayHappy));
        
        const char *newType = method_getTypeEncoding(newMethod);
        
        return class_addMethod(self, sel, newIMP, newType);
    }
    
    return [super resolveInstanceMethod:sel];
}
複製代碼

2、快速轉發

若是咱們繼續上面的思路走的話,會發現這個方法已經走完了,線索就斷掉了。。。

其實在lookUpImpOrForward裏有一個log_and_fill_cache方法,這個方法裏面有一個logMessageSend方法調用, 這一系列操做(方法就不一一展現了,太多了,直接到達目的地)中有一步驟是打印日誌,而日誌存儲的位置:

snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **());**
複製代碼

經過全局探索,發現是否打印的開關是這麼一句代碼:

instrumentObjcMessageSends(BOOL flag)
複製代碼

因而,咱們跑一下代碼看一下,(隨便建立一份工程,源碼跑不動)準備工做:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        LGPerson *objc = [[LGPerson alloc] init];
        //打開開關
        instrumentObjcMessageSends(true);
        //這個實例方法,只聲明瞭,沒有實現,跑起來確定會崩潰
        [objc sayHello];
        //關閉開關
        instrumentObjcMessageSends(false);
        
        
        
    }
    return 0;
} 
複製代碼

咱們運行,找一下/tmp/msgSends這個路徑存儲的日誌文件看一下:

從日誌中能夠看出,動態方法解析以後調用了forwardingTargetForSelector方法。咱們去官方看一下這段代碼的含義:

大體意思就是,找一個備用的接收者,即返回一個實現了這個方法的對象。

代碼示例:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(aSelector);
    if ([name isEqual:@"sayHello"]) {
    	//Jack這個類 聲明並實現了 sayHello這個方法
        return [Jack alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
複製代碼

若是沒有這個備用接收者,或者沒有實現這個方法,就會走到慢速轉發

3、慢速轉發

在快速轉發的官方介紹中,我有圈起來,若是不實現快速轉發,會調用forwardInvocation:在這方法,那咱們來看一下這個方法的官方介紹:

官方介紹中也圈起來了,要重寫forwardInvocation:這個方法,就必須同時重寫methodSignatureForSelector:方法,咱們繼續看一下官方介紹:

慢速流程流程就是先走methodSignatureForSelector提供一個方法簽名,而後走forwardInvocation經過NSInvocation來實現消息的轉發

代碼示例:

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(aSelector);
    if ([name isEqual:@"sayHello"]) {
        //方法簽名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//其實這個方法能夠空着不寫
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"%s ",__func__);
    
    SEL aSelector = [anInvocation selector];

    if ([[Jack alloc] respondsToSelector:aSelector]){
        
        [anInvocation invokeWithTarget:[Jack alloc]];
        
    }else{
        
        [super forwardInvocation:anInvocation];
    }
      
    
}
複製代碼

4、坑點

有這麼一個狀況,resolveInstanceMethod調用了2次

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s ",__func__); 
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s ",__func__); 
    return [super forwardingTargetForSelector:aSelector];
}


//先拿到方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(aSelector);
    
    if ([name isEqual:@"sayHello"]) {
        //方法簽名  v-返回值,@-傳入對象,:-傳入sel
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}
//經過anInvocation進行消息轉發,也能夠空着這個方法不做處理
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"%s ",__func__);
  
}
複製代碼

緣由: 在調用methodSignatureForSelector方法的時候,咱們傳了一個type,這時候系統會去進行匹配,走到class_getInstanceMethod這個方法裏面:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
複製代碼

又會走到lookUpImpOrForward方法,最終仍是進入resolveMethod_locked這個方法,而後就會再走一次 resolveInstanceMethod方法。

** 坑點總結**:若是出現走兩次resolveInstanceMethod方法的狀況,是由於在簽名以後,系統作了一些操做:會根據咱們傳入的簽名type進行匹配,調用class_getInstanceMethod方法就會再走一次。

5、總結

前提:整個查找流程沒有找到該方法,而後進入消息轉發流程

  1. 首先來到動態方法解析,添加一個方法實現(給sel指定一個imp

    • 對象方法,調用+(BOOL)resolveInstanceMethod:(SEL)sel方法
    • 類方法,調用+(BOOL)resolveClassMethod:(SEL)sel方法
  2. 快速轉發,若是動態方法解析沒有找到對應的處理方法,就會來到這裏

    • 調用- (id)forwardingTargetForSelector:(SEL)aSelector方法,return一個備用接收者
  3. 慢速轉發,若是快速轉發也沒有處理,就會來到這裏

    • 第一步,調用-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法拿到方法簽名
    • 第二步,調用-(void)forwardInvocation:(NSInvocation *)anInvocation經過NSInvocation來實現消息轉發
相關文章
相關標籤/搜索