Runtime底層原理--動態方法解析總結

方法的底層會編譯成消息,消息進行遞歸,先從實例方法開始查找,到父類最後到NSObject。如果在彙編部分快速查找沒有找到IMP,就會進入C/C++中的動態方法解析進入lookUpImpOrForward方法進行遞歸。

動態方法解析

動態方法解析分爲實例方法和類方法兩種。

實例方法查找imp流程和動態方法解析

比如執行一個Student實例方法eat,會先去這個類中查找是否有該方法(sel),如果有則進行存儲以便下次直接從彙編部分快速查找。

// Try this class's cache.
    // Student元類 - 父類 (根元類) -- NSObject
    // resovleInstance 防止遞歸 --
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

如果沒有sel那麼接下來去父類(直到NSObject)的緩存和方法列表找查找。如果在父類中找到先緩存再執行done.

// 元類的父類 - NSObject 是否有 實例方法
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // 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;
                }

如果最終還是沒找到,則會進入動態方法解析_class_resolveMethod,先判斷當前cls對象是不是元類,也就是如果是對象方法會走到_class_resolveInstanceMethod方法,

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
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);
        }
    }
}

如果元類,那麼執行_class_resolveInstanceMethod(cls, sel, inst)方法,該方法會執行lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/),查找當前的cls的isa是否實現了resolveInstanceMethod,也就是是否有自定義實現、是否重寫了。如果查到了就會給類對象發送消息objc_msgSend,調起resolveInstanceMethod方法

/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

lookUpImpOrNil的內部是通過lookUpImpOrForward方法進行查找,再次回到遞歸調用。

如果還是沒查到,這裏就不會再次進入動態方法解析(注:如果再次進入動態方法解析會形成死遞歸),首先對cls的元類進行查找,然後元類的父類,也就是根元類(系統默認實現的虛擬的)進行查找、最終到NSObjece,只不過NSObjece中默認實現resolveInstanceMethod方法返回NO,也就是此時在元類進行查找的時候找到了resolveInstanceMethod方法,並停止繼續查找,這就是爲什麼動態方法解析後的遞歸沒有再次進入動態方法解析的原因。如果最終還是沒有找到SEL_resolveInstanceMethod則說明程序有問題,直接返回。下面是isa走位圖:

如果找到的imp不是轉發的imp,則返回imp。
舉個例子:
在Student中有個對象run方法,但是並沒有實現,當調用run方法時,最終沒有找到imp會崩潰。通過動態方法解析,實現run方法

#pragma mark - 動態方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"動態方法解析 - %@",self);
    if (sel == @selector(run)) {
        // 我們動態解析對象方法
        NSLog(@"對象方法 run 解析走這裏");
        SEL readSEL = @selector(readBook);
        Method readM= class_getInstanceMethod(self, readSEL);
        IMP readImp = method_getImplementation(readM);
        const char *type = method_getTypeEncoding(readM);
        return class_addMethod(self, sel, readImp, type);
    }
    return [super resolveInstanceMethod:sel];
}

此時只是給對象方法添加了一個imp,接下來再次進入查找imp流程,重複之前的操作,只不過現在對象方法已經有了imp。

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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);

    // 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// ...省略N行代碼

動態方法解析的實質: 經過漫長的查找並沒有找到sel的imp,系統會發送resolveInstanceMethod消息,爲了防止系統崩潰,可以在該方法內對sel添加imp,系統會自動再次查找imp。

類方法查找imp流程和動態方法解析

類方法查找imp流程和實例方法查找imp前面流程一樣,也是從彙編部分快速查找,之後判斷cls是不是元類,在元類方法列表中查找,如果元類中沒有當前的sel,就去元類的父類中查找,還沒有就去根元類的父類NSObject中查找,此時查找的就是NSObject中是否有這個實例對象方法,如果NSObject中也沒有就會進入動態方法解析_class_resolveMethod。類對象這裏的cls和對象方法不一樣,因爲cls是元類所以直接走_class_resolveClassMethod方法。進入_class_resolveClassMethod方法還是先判斷resolveClassMethod方法是否有實現,之後發送消息objc_msgSend,這裏和實例方法有所區別,類方法會執行_class_getNonMetaClass方法,內部實現getNonMetaClassgetNonMetaClass會判斷當前cls是不是NSObject,判斷當前的cls是不是根元類,也就是自己,接下來判斷inst類對象,判斷inst類對象的isa如果不是元類,那麼返回類對象的父類,不是就返回類對象。在_class_resolveClassMethod方法中添加了imp後還是和實例方法一樣,再次進入重新查找流程,此時如果還是沒有,那麼類方法還會再一次的進入_class_resolveInstanceMethod方法,和實例方法不同的是resolveInstanceMethod方法內部的cls是元類,所以找的方法也就是- (BOOL)resolveClassMethod:(SEL)sel,可以在NSObject中添加+ (BOOL)resolveClassMethod:(SEL)sel方法,這樣無論類方法還是實例方法都會走到這裏,可以作爲防崩潰的處理

/***********************************************************************
* getNonMetaClass
* Return the ordinary class for this class or metaclass. 
* `inst` is an instance of `cls` or a subclass thereof, or nil. 
* Non-nil inst is faster.
* Used by +initialize. 
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class getNonMetaClass(Class metacls, id inst)
{
    static int total, named, secondary, sharedcache;
    runtimeLock.assertLocked();

    realizeClass(metacls);

    total++;

    // return cls itself if it's already a non-meta class
    if (!metacls->isMetaClass()) return metacls;

    // metacls really is a metaclass

    // special case for root metaclass
    // where inst == inst->ISA() == metacls is possible
    if (metacls->ISA() == metacls) {
        Class cls = metacls->superclass;
        assert(cls->isRealized());
        assert(!cls->isMetaClass());
        assert(cls->ISA() == metacls);
        if (cls->ISA() == metacls) return cls;
    }

    // use inst if available
    if (inst) {
        Class cls = (Class)inst;
        realizeClass(cls);
        // cls may be a subclass - find the real class for metacls
        while (cls  &&  cls->ISA() != metacls) {
            cls = cls->superclass;
            realizeClass(cls);
        }
        if (cls) {
            assert(!cls->isMetaClass());
            assert(cls->ISA() == metacls);
            return cls;
        }

我們在Student類中添加未實現的類方法walk,在NSObject類中添加一個對象方法walk,運行程序不會崩潰。類方法先遞歸,開始找父類,最終在NSObject類中好到對象方法walk

TIP:對象方法存儲在類中,類方法存儲在元類裏面,類對象以實例方法的形式存儲在元類中。可以通過輸出class_getInstanceMethod方法和class_getClassMethod方法的imp指針來驗證,當然源碼也可以解釋在cls的元類中查找實例方法

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

還可以通過LLDB進行驗證,動態方法解析的時候執行lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)方法,這裏的cls就是inst的元類

#   define ISA_MASK        0x00007ffffffffff8ULL

// -------------------------------------------------
#if SUPPORT_NONPOINTER_ISA

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

這裏看到初始化的時候isa.bits & ISA_MASK,我們先後打印cls和inst的信息,也可以驗證當前指針指向當前的元類。

動態方法解析作用

適用於重定向,也可以做防崩潰處理,也可以做一些錯誤日誌收集等等。動態方法解析本質就是提供機會(任何沒有實現的方法都可以重新實現)。

該文章爲記錄本人的學習路程,希望能夠幫助大家,也歡迎大家點贊留言交流!!!文章地址:https://www.jianshu.com/p/a7db9f0c82d6

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