runtime消息發送&消息轉發機制底層源碼解析

     ?文章有點長,請做好心理準備...Let`s go!

    基於Runtime機制,OC的對象發送消息就會通過一系列操作,根據對象從相應的類中查找方法對應的列表(類即類對象,方法存儲在元類的方法列表中),方法列表實質是一個哈希表,通過SEL查找到IMP(即函數指針),返回相應的實現。

    案例:有一個類Person,Person類有一個類方法walk,調用如下:

[Person walk]; 

    Person對象在調用run的時候會依次進行方法的查找,此時的查找有分爲快速查找(彙編cache_t查找)和慢速查找(C/C++查找),若找到則返回IMP,找不到則進行消息轉發流程,如果消息轉發沒有處理,則直接Crash掉。

    一.方法查找
    在調用方法的地方打一個breakpoint,使用Debug下的Always Show Disassembly查看一下彙編源碼如下:

    objc_msgSend 利用runtime機制給對象發送消息的函數,_objc_msgSend之所以用匯編寫是因爲,寫一個函數,保留未知的參數,跳轉到任意的指針,參數是在運行時傳遞的,C是無法實現的並且也沒有滿足做這件事的必要性,還需要向底層編譯,彙編有寄存器,可以直接進行保存、直接進行編譯,比較快,彙編直接進行了一系列位移操作,直接操作內存,就非常快。全局搜索_objc_msgSend方法查看彙編源碼,這裏原則arm64進行分析。

    如上圖,可以看到進入_objc_msgSend之後,發現執行了LNilOrTagged,很明顯是一個判斷,判斷是否爲空,或者是否是Tagged Point類型,這個LNilOrTagged執行了什麼呢?

   

    LReturnZero 判斷是否爲nil,如果爲空,直接就返回了,不會在進行方法的查找。LGetIsaDone 開始處理isa,外物皆對象,萬物皆有isa。

LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

    LGetIsaDone 加載完畢後調用了CacheLookup NORMAL,CacheLookup 主要是開始從緩存列表中查找。CacheLookup是一個宏,有三種情況,NORMAL|GETIMP|LOOKUP,而我們剛剛傳入的是NORMAL。

    CacheHit是一個宏,表示命中了,找到了imp,就會調用TailCallCachedImp返回imp。

     如果沒有找到,就會調用CheckMissCheckMiss也是一個宏,由於前面傳遞的是NORMAL,所以會調用__objc_msgSend_uncached

    開始調用__objc_msgSend_uncached,這個裏面有一個非常重要的調用MethodTableLookup,在TailCallFunctionPointer之前調用,說明開始進行方法列表的的查找。
 

    MethodTableLookup也是一個宏,methodList內部比較復發,裏面存儲的是methode_t,SEL 和 IMP以哈希表的方式存儲。

    此時搜索__class_lookupMethodAndLoadCache3已經查找不到了,這個時候我們就要考慮C/C++了,換一個思維,這裏有一個”__“的區別。搜索“_class_lookupMethodAndLoadCache”就會發現找到了。

    _class_lookupMethodAndLoadCache3這個方法返回的類型就是IMP。注意:上面3個參數:

    cls:因爲我們已經通過彙編已經編譯到了我們所有的結構,如果沒有編譯到,上面就不會有isa了,也就是YES。

    cache:如果有cache,上面就返回了,所以這個肯定是沒有的,也就是NO。

    resolver:動態解析,如果沒有即將進入下一個階段,動態方法解析和消息轉發機制了。

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {                      //進入到這裏,傳遞的參數cache本身就是NO
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {   //如果這個類沒有實現,就去實現這個類,裏面有class_rw_t
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }

 //重點!!!
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
    //imp ?設計
    //併發
    //remap(cls) -- 重映射   一開始找的時候的的確確是有的,但是沒有找到imp,我們在創建類的時候,哈希表會進行修復,對類進行重映射,可能就有了
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.      //先從本類的方法列表中開始查找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);   //for循環進行查找方法,返回Method
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);   //給我們的緩存賦值
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.  //從父類的方法列表中查找
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;     
             curClass = curClass->superclass)    //只要不是nil,因爲上帝類NSObject最後指向了nil
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.     //如果都沒有找到,就進行解析

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);   //當調用這個方法的時候,系統自動Call +resolveClassMethod or +resolveInstanceMethod.
        runtimeLock.lock();
        triedResolver = YES;
        goto retry;
    }

    ...

    return imp;
}

    二.動態方法解析

    如果經過了一列操作,沒有找到imp,就會進行動態方法解析_class_resolveMethod

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {      //判斷是否是元類
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

Person類裏面進行重寫resolveInstanceMethodresolveClassMethod

重寫了着兩個方法以後,調用類方法walk的時候,打印了兩次,意味着resolveClassMethod執行了兩次,Why???

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);  //系統幫你給對象發送了消息

    ...
}

    首先看一下消息轉發流程圖:

 

    第一次調用,是沒有問題的,第二次調用是在消息無法處理以後又調用了一次。爲了印證這個理論,在打印的地方打一個breakpoint,打印一下堆棧調用信息。

    resolveClassMethod是在上面分析的_objc_msgSend_uncached方法之後執行了第一次調用,再接着往下運行。

    第二次調用就是在methodSignatureForSelector,也就是第二次消息轉發沒有處理之後開始了resolveClassMethod這個方法第二次調用。

    消息轉發首先會進行動態方法解析resolveClassMethod,處理這個方法的處理很簡單,對select進行方法的轉移就可以了。這裏對類方法進行重寫,實例方法實現方式一樣,就不粘貼代碼了。

//重寫resolveClassMethod方法
+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(helloWord)) {
        SEL helloWordSEL = @selector(helloWord);
        Method helloWordMet = class_getClassMethod(self, helloWordSEL);
        IMP helloWordIMP = method_getImplementation(helloWordMet);
        const char *type = method_getTypeEncoding(helloWordMet);
        return class_addMethod(self, helloWordSEL, helloWordIMP, type);
    }
    return [super resolveClassMethod:sel];
}

//自定義方法
+ (void)helloWord{
    NSLog(@"%s",__func__);
}

    當重寫了resolveClassMethod以後,回到我們當初的lookUpImpOrForward方法內部。

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
....

 //重點
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   
...

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);   //動態方法解析
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;   //回到了retry,重新查找方法
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;   //如果動態方法解析過程沒有處理,就會進入到這裏--消息轉發
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}

    查看lookUpImpOrForward的源碼發現,執行完動態方法解析或消息轉發以後,又回到了retry進行方法的查找,剛剛在動態方法解析裏面已經對類添加了方法,實現了select的轉移,直接調用,不會再Crash了。

    如果_class_resolveMethod動態方法沒有進行處理,就會進行消息轉發_objc_msgForward_impcache。這一塊只有彙編調用,沒有源碼實現,源碼實現閉源了。但是我們可以通過instrumentObjcMessageSends這個函數來打印底層調用了那些信息,最後會存儲在private/tmp文件夾下。

#import <Foundation/Foundation.h>
#import "Student.h"
#include <objc/message.h> 

extern void instrumentObjcMessageSends(BOOL);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        [Person  walk];
        instrumentObjcMessageSends(NO);

    }
    return 0;
}

+ Person NSObject initialize
+ Person Person resolveClassMethod:
+ Person Person resolveClassMethod:
+ NSObject NSObject resolveClassMethod:
+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:
+ Person Person forwardingTargetForSelector:
+ Person Person forwardingTargetForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
+ NSObject NSObject forwardingTargetForSelector:
+ Person Person methodSignatureForSelector:
+ Person Person methodSignatureForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
...

    首先實現第一步,forwardingTargetForSelector

//重寫forwardingTargetForSelector方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(run)) {
        // 轉發給我們的Student 對象
        return [Student new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

    因此可以在forwardingTargetForSelector裏面進行自定義處理,還可以進行一系列cash的手機、防止崩潰等。

    如果forwardingTargetForSelector依然沒有處理,那就開始處理消息簽名,消息轉發。

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(walk)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    //重定向  
    NSLog(@"%s",__func__);
    NSString *sto = @"方法來了奧~";
    anInvocation.target = [Student class];
    [anInvocation setArgument:&sto atIndex:2];
    NSLog(@"%@",anInvocation.methodSignature);
    anInvocation.selector = @selector(run:);
    [anInvocation invoke];
}

    最後附上思維導圖

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章