Runtime底層原理--動(dòng)態(tài)方法解析總結(jié)

動(dòng)態(tài)方法解析總結(jié)

方法的底層會(huì)編譯成消息,消息進(jìn)行遞歸,先從實(shí)例方法開始查找腹纳,到父類最后到NSObject。如果在匯編部分快速查找沒有找到IMP驱犹,就會(huì)進(jìn)入C/C++中的動(dòng)態(tài)方法解析進(jìn)入lookUpImpOrForward方法進(jìn)行遞歸嘲恍。

動(dòng)態(tài)方法解析

動(dòng)態(tài)方法解析分為實(shí)例方法和類方法兩種。

實(shí)例方法查找imp流程和動(dòng)態(tài)方法解析

比如執(zhí)行一個(gè)Student實(shí)例方法eat着绷,會(huì)先去這個(gè)類中查找是否有該方法(sel),如果有則進(jìn)行存儲(chǔ)以便下次直接從匯編部分快速查找蛔钙。

// 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)的緩存和方法列表找查找。如果在父類中找到先緩存再執(zhí)行done.

// 元類的父類 - NSObject 是否有 實(shí)例方法
        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;
                }

如果最終還是沒找到荠医,則會(huì)進(jìn)入動(dòng)態(tài)方法解析_class_resolveMethod吁脱,先判斷當(dāng)前cls對象是不是元類,也就是如果是對象方法會(huì)走到_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);
        }
    }
}

如果元類兼贡,那么執(zhí)行_class_resolveInstanceMethod(cls, sel, inst)方法,該方法會(huì)執(zhí)行lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)娃胆,查找當(dāng)前的cls的isa是否實(shí)現(xiàn)了resolveInstanceMethod遍希,也就是是否有自定義實(shí)現(xiàn)、是否重寫了里烦。如果查到了就會(huì)給類對象發(fā)送消息objc_msgSend凿蒜,調(diào)起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的內(nèi)部是通過lookUpImpOrForward方法進(jìn)行查找禁谦,再次回到遞歸調(diào)用。

如果還是沒查到废封,這里就不會(huì)再次進(jìn)入動(dòng)態(tài)方法解析(注:如果再次進(jìn)入動(dòng)態(tài)方法解析會(huì)形成死遞歸)州泊,首先對cls的元類進(jìn)行查找,然后元類的父類漂洋,也就是根元類(系統(tǒng)默認(rèn)實(shí)現(xiàn)的虛擬的)進(jìn)行查找遥皂、最終到NSObjece,只不過NSObjece中默認(rèn)實(shí)現(xiàn)resolveInstanceMethod方法返回NO刽漂,也就是此時(shí)在元類進(jìn)行查找的時(shí)候找到了resolveInstanceMethod方法演训,并停止繼續(xù)查找,這就是為什么動(dòng)態(tài)方法解析后的遞歸沒有再次進(jìn)入動(dòng)態(tài)方法解析的原因贝咙。如果最終還是沒有找到SEL_resolveInstanceMethod則說明程序有問題样悟,直接返回。下面是isa走位圖:

isa走位流程

如果找到的imp不是轉(zhuǎn)發(fā)的imp颈畸,則返回imp乌奇。
舉個(gè)例子:
在Student中有個(gè)對象run方法,但是并沒有實(shí)現(xiàn)眯娱,當(dāng)調(diào)用run方法時(shí)礁苗,最終沒有找到imp會(huì)崩潰。通過動(dòng)態(tài)方法解析徙缴,實(shí)現(xiàn)run方法

#pragma mark - 動(dòng)態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"動(dòng)態(tài)方法解析 - %@",self);
    if (sel == @selector(run)) {
        // 我們動(dòng)態(tài)解析對象方法
        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];
}

此時(shí)只是給對象方法添加了一個(gè)imp试伙,接下來再次進(jìn)入查找imp流程,重復(fù)之前的操作于样,只不過現(xiàn)在對象方法已經(jīng)有了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行代碼

動(dòng)態(tài)方法解析的實(shí)質(zhì): 經(jīng)過漫長的查找并沒有找到sel的imp,系統(tǒng)會(huì)發(fā)送resolveInstanceMethod消息穿剖,為了防止系統(tǒng)崩潰蚤蔓,可以在該方法內(nèi)對sel添加imp,系統(tǒng)會(huì)自動(dòng)再次查找imp糊余。

類方法查找imp流程和動(dòng)態(tài)方法解析

類方法查找imp流程和實(shí)例方法查找imp前面流程一樣秀又,也是從匯編部分快速查找,之后判斷cls是不是元類贬芥,在元類方法列表中查找吐辙,如果元類中沒有當(dāng)前的sel,就去元類的父類中查找蘸劈,還沒有就去根元類的父類NSObject中查找昏苏,此時(shí)查找的就是NSObject中是否有這個(gè)實(shí)例對象方法,如果NSObject中也沒有就會(huì)進(jìn)入動(dòng)態(tài)方法解析_class_resolveMethod。類對象這里的cls和對象方法不一樣贤惯,因?yàn)閏ls是元類所以直接走_class_resolveClassMethod方法洼专。進(jìn)入_class_resolveClassMethod方法還是先判斷resolveClassMethod方法是否有實(shí)現(xiàn),之后發(fā)送消息objc_msgSend救巷,這里和實(shí)例方法有所區(qū)別壶熏,類方法會(huì)執(zhí)行_class_getNonMetaClass方法句柠,內(nèi)部實(shí)現(xiàn)getNonMetaClass浦译,getNonMetaClass會(huì)判斷當(dāng)前cls是不是NSObject,判斷當(dāng)前的cls是不是根元類溯职,也就是自己精盅,接下來判斷inst類對象,判斷inst類對象的isa如果不是元類谜酒,那么返回類對象的父類叹俏,不是就返回類對象。在_class_resolveClassMethod方法中添加了imp后還是和實(shí)例方法一樣僻族,再次進(jìn)入重新查找流程粘驰,此時(shí)如果還是沒有,那么類方法還會(huì)再一次的進(jìn)入_class_resolveInstanceMethod方法述么,和實(shí)例方法不同的是resolveInstanceMethod方法內(nèi)部的cls是元類蝌数,所以找的方法也就是- (BOOL)resolveClassMethod:(SEL)sel,可以在NSObject中添加+ (BOOL)resolveClassMethod:(SEL)sel方法度秘,這樣無論類方法還是實(shí)例方法都會(huì)走到這里顶伞,可以作為防崩潰的處理

/***********************************************************************
* 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類中添加未實(shí)現(xiàn)的類方法walk剑梳,在NSObject類中添加一個(gè)對象方法walk唆貌,運(yùn)行程序不會(huì)崩潰。類方法先遞歸垢乙,開始找父類锨咙,最終在NSObject類中好到對象方法walk

TIP:對象方法存儲(chǔ)在類中追逮,類方法存儲(chǔ)在元類里面酪刀,類對象以實(shí)例方法的形式存儲(chǔ)在元類中⊙蛞迹可以通過輸出class_getInstanceMethod方法和class_getClassMethod方法的imp指針來驗(yàn)證蓖宦,當(dāng)然源碼也可以解釋在cls的元類中查找實(shí)例方法

/***********************************************************************
* 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進(jìn)行驗(yàn)證,動(dòng)態(tài)方法解析的時(shí)候執(zhí)行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
}

這里看到初始化的時(shí)候isa.bits & ISA_MASK稠茂,我們先后打印cls和inst的信息,也可以驗(yàn)證當(dāng)前指針指向當(dāng)前的元類。

LLDB信息

動(dòng)態(tài)方法解析作用

適用于重定向睬关,也可以做防崩潰處理诱担,也可以做一些錯(cuò)誤日志收集等等。動(dòng)態(tài)方法解析本質(zhì)就是提供機(jī)會(huì)(任何沒有實(shí)現(xiàn)的方法都可以重新實(shí)現(xiàn))电爹。

該文章為記錄本人的學(xué)習(xí)路程蔫仙,希望能夠幫助大家,也歡迎大家點(diǎn)贊留言交流Xぢ帷R“睢!文章地址:http://www.reibang.com/p/a7db9f0c82d6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屎勘,一起剝皮案震驚了整個(gè)濱河市施籍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌概漱,老刑警劉巖丑慎,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓤摧,居然都是意外死亡竿裂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門照弥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻异,“玉大人,你說我怎么就攤上這事产喉∥骊” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵曾沈,是天一觀的道長这嚣。 經(jīng)常有香客問我,道長塞俱,這世上最難降的妖魔是什么姐帚? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮障涯,結(jié)果婚禮上罐旗,老公的妹妹穿的比我還像新娘。我一直安慰自己唯蝶,他們只是感情好九秀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粘我,像睡著了一般鼓蜒。 火紅的嫁衣襯著肌膚如雪痹换。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天都弹,我揣著相機(jī)與錄音娇豫,去河邊找鬼。 笑死畅厢,一個(gè)胖子當(dāng)著我的面吹牛冯痢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播框杜,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼浦楣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霸琴?” 一聲冷哼從身側(cè)響起椒振,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梧乘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庐杨,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡选调,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灵份。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仁堪。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖填渠,靈堂內(nèi)的尸體忽然破棺而出弦聂,到底是詐尸還是另有隱情,我是刑警寧澤氛什,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布莺葫,位于F島的核電站,受9級特大地震影響枪眉,放射性物質(zhì)發(fā)生泄漏捺檬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一贸铜、第九天 我趴在偏房一處隱蔽的房頂上張望堡纬。 院中可真熱鬧,春花似錦蒿秦、人聲如沸烤镐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炮叶。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悴灵,已是汗流浹背扛芽。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留积瞒,地道東北人川尖。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像茫孔,于是被迫代替她去往敵國和親叮喳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354