iOS底層原理探索— Runtime之消息機(jī)制

探索底層原理栅屏,積累從點(diǎn)滴做起。大家好,我是Mars手蝎。

往期回顧

iOS底層原理探索—OC對(duì)象的本質(zhì)
iOS底層原理探索—class的本質(zhì)
iOS底層原理探索—KVO的本質(zhì)
iOS底層原理探索— KVC的本質(zhì)
iOS底層原理探索— Category的本質(zhì)(一)
iOS底層原理探索— Category的本質(zhì)(二)
iOS底層原理探索— 關(guān)聯(lián)對(duì)象的本質(zhì)
iOS底層原理探索— block的本質(zhì)(一)
iOS底層原理探索— block的本質(zhì)(二)
iOS底層原理探索— Runtime之isa的本質(zhì)
iOS底層原理探索— Runtime之class的本質(zhì)

今天繼續(xù)帶領(lǐng)大家探索iOS之Runtime的本質(zhì)显熏。

前言

OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言雄嚣,它的動(dòng)態(tài)性是基于RuntimeAPIRuntime在我們的實(shí)際開發(fā)中占據(jù)著重要的地位喘蟆,在面試過程中也經(jīng)常遇到Runtime相關(guān)的面試題缓升,我們?cè)谥皫灼诘奶剿鞣治鰰r(shí)也經(jīng)常會(huì)到Runtime的底層源碼中查看相關(guān)實(shí)現(xiàn)。Runtime對(duì)于iOS開發(fā)者的重要性不言而喻蕴轨,想要學(xué)習(xí)和掌握Runtime的相關(guān)技術(shù)港谊,就要從Runtime底層的一些常用數(shù)據(jù)結(jié)構(gòu)入手。掌握了它的底層結(jié)構(gòu)橙弱,我們學(xué)習(xí)起來(lái)也能達(dá)到事半功倍的效果歧寺。今天研究OC消息機(jī)制

消息機(jī)制

OC語(yǔ)言中方法調(diào)用通過消息機(jī)制來(lái)實(shí)現(xiàn)棘脐,方法調(diào)用其實(shí)都是轉(zhuǎn)換為 objc_msgSend函數(shù)調(diào)用斜筐。

objc_msgSend函數(shù).png

OC消息機(jī)制可以分為一下三個(gè)階段:

1、消息發(fā)送階段:從類及父類的方法緩存列表及方法列表查找方法蛀缝;

2顷链、動(dòng)態(tài)解析階段:如果消息發(fā)送階段沒有找到方法,則會(huì)進(jìn)入動(dòng)態(tài)解析階段内斯,負(fù)責(zé)動(dòng)態(tài)的添加方法實(shí)現(xiàn)蕴潦;

3像啼、消息轉(zhuǎn)發(fā)階段:如果也沒有實(shí)現(xiàn)動(dòng)態(tài)解析方法,則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段潭苞,將消息轉(zhuǎn)發(fā)給可以處理消息的接受者來(lái)處理忽冻;

如果消息轉(zhuǎn)發(fā)也沒有實(shí)現(xiàn),就會(huì)報(bào)出經(jīng)典的錯(cuò)誤:unrecognzied selector sent to instance此疹,方法找不到的錯(cuò)誤僧诚,無(wú)法識(shí)別消息。

接下來(lái)我們通過源碼分析消息機(jī)制的三個(gè)階段分別是如何實(shí)現(xiàn)的蝗碎。

1湖笨、消息發(fā)送

在項(xiàng)目中方法調(diào)用的頻率很高,所以為了能夠提升效率蹦骑,在底層代碼中objc_msgSend函數(shù)的實(shí)現(xiàn)是通過匯編語(yǔ)言編寫的慈省,我們?cè)谠创a中找到objc-msg-arm64.s匯編文件,來(lái)具體分析一下objc_msgSend函數(shù)的實(shí)現(xiàn)眠菇。

objc_msgSend內(nèi)部實(shí)現(xiàn).png

objc_msgSend函數(shù)中首先判斷消息接收者receiver是否為空边败。如果傳入的消息接受者為nil則會(huì)執(zhí)行LNilOrTaggedLNilOrTagged內(nèi)部會(huì)執(zhí)行LReturnZero捎废,而LReturnZero內(nèi)部則直接return 0笑窜。

如果傳入的消息接收者receiver不為空則通過消息接收者receiverisa指針找到消息接收者的class,執(zhí)行CacheLookup從方法緩存中取查找登疗。如果在方法緩存列表找到則執(zhí)行CacheHit排截,調(diào)用方法或者返回函數(shù)地址;如果找到就執(zhí)行CheckMiss辐益。CheckMiss內(nèi)調(diào)用__objc_msgSend_uncached断傲,方法沒有被緩存。

__objc_msgSend_uncached內(nèi)會(huì)執(zhí)行MethodTableLookup荷腊,去方法列表中查找艳悔。MethodTableLookup內(nèi)部的核心代碼__class_lookupMethodAndLoadCache3也就是c語(yǔ)言函數(shù)_class_lookupMethodAndLoadCache3(雙下劃線開頭變成單下劃線)。

以上分析我們用簡(jiǎn)單的流程圖來(lái)總結(jié):

消息發(fā)送階段流程.png

接下來(lái)我們進(jìn)入_class_lookupMethodAndLoadCache3函數(shù)女仰,分析是如何從方法列表中查找方法猜年。

_class_lookupMethodAndLoadCache3函數(shù)

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

函數(shù)內(nèi)部調(diào)用lookUpImpOrForward方法,傳入三個(gè)BOOL類型的參數(shù)疾忍。

lookUpImpOrForward 函數(shù)

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    //接收傳入的參數(shù)乔外, initialize = YES , cache = NO , resolver = YES
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();

    // 緩存查找, 因?yàn)閏ache傳入的為NO, 這里不會(huì)進(jìn)行緩存查找, 因?yàn)樵趨R編語(yǔ)言中CacheLookup已經(jīng)查找過
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.read();
    if (!cls->isRealized()) {
        runtimeLock.unlockRead();
        runtimeLock.write();
        realizeClass(cls);
        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }

 retry:    
    runtimeLock.assertReading();

    // 防止動(dòng)態(tài)添加方法败匹,緩存會(huì)變化巢株,再次查找緩存。
    imp = cache_getImp(cls, sel);
    // 如果找到imp方法地址, 直接調(diào)用done, 返回方法地址
    if (imp) goto done;

    // 查找方法列表, 傳入類對(duì)象和方法名
    {
        // 根據(jù)sel去類對(duì)象里面查找方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 如果方法存在恢口,則緩存方法,
            // 內(nèi)部調(diào)用的就是 cache_fill差购。
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            // 方法緩存之后, 取出函數(shù)地址imp并返回
            imp = meth->imp;
            goto done;
        }
    }

    // 如果類方法列表中沒有找到, 則去父類的緩存中或方法列表中查找方法
    {
        unsigned attempts = unreasonableClassCount();
        // 如果父類緩存列表及方法列表均找不到方法四瘫,則去父類的父類去查找。
        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.");
            }
            
            // 查找父類的緩存
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 在父類中找到方法, 在本類中緩存方法, 注意這里傳入的是cls, 將方法緩存在本類緩存列表中, 而非父類中
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    // 執(zhí)行done, 返回imp
                    goto done;
                }
                else {
                    // 跳出循環(huán), 停止搜索
                    break;
                }
            }
            
            // 查找父類的方法列表
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                // 同樣拿到方法, 在本類進(jìn)行緩存
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                // 執(zhí)行done, 返回imp
                goto done;
            }
        }
    }
    
    // ---------------- 消息發(fā)送階段完成,沒有找到方法實(shí)現(xiàn)欲逃,進(jìn)入動(dòng)態(tài)解析階段 ---------------------
    //首先檢查是否已經(jīng)被標(biāo)記為動(dòng)態(tài)方法解析找蜜,如果沒有才會(huì)進(jìn)入動(dòng)態(tài)方法解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        //將triedResolver標(biāo)記為YES,下次就不會(huì)再進(jìn)入動(dòng)態(tài)方法解析
        triedResolver = YES;
        goto retry;
    }

    // ---------------- 動(dòng)態(tài)解析階段完成,進(jìn)入消息轉(zhuǎn)發(fā)階段 ---------------------
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();
    // 返回方法地址
    return imp;
}

getMethodNoSuper_nolock 函數(shù)

方法列表中查找方法

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    // cls->data() 得到的是 class_rw_t
    // class_rw_t->methods 得到的是methods二維數(shù)組
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
         // mlists 為 method_list_t
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }
    return nil;
}

getMethodNoSuper_nolock函數(shù)中通過遍歷方法列表拿到method_list_t最終通過search_method_list函數(shù)查找方法
search_method_list函數(shù)

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    // 如果方法列表已經(jīng)排序好了稳析,則通過二分查找法查找方法洗做,以節(jié)省時(shí)間
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        //  如果方法列表沒有排序好就遍歷查找
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
    return nil;
}

findMethodInSortedMethodList函數(shù)內(nèi)二分查找實(shí)現(xiàn)原理

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // >>1 表示將變量n的各個(gè)二進(jìn)制位順序右移1位,最高位補(bǔ)二進(jìn)制0彰居。
    // count >>= 1 如果count為偶數(shù)則值變?yōu)?count / 2)诚纸。如果count為奇數(shù)則值變?yōu)?count-1) / 2 
    for (count = list->count; count != 0; count >>= 1) {
        // probe 指向數(shù)組中間的值
        probe = base + (count >> 1);
        // 取出中間method_t的name,也就是SEL
        uintptr_t probeValue = (uintptr_t)probe->name;
        if (keyValue == probeValue) {
            // 取出 probe
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
           // 返回方法
            return (method_t *)probe;
        }
        // 如果keyValue > probeValue 則折半向后查詢
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

通過以上分析陈惰,我們了解了消息機(jī)制中的第一階段消息發(fā)送階段畦徘,下面我們用一張圖來(lái)總結(jié)一下整體流程:

消息發(fā)送.png

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

當(dāng)在類和父類的方法緩存列表抬闯、方法列表中都找不到方法時(shí)旧烧,就會(huì)進(jìn)入動(dòng)態(tài)方法解析階段。我們?cè)谙l(fā)送階段源碼中看到画髓,進(jìn)入動(dòng)態(tài)方法解析階段是通過函數(shù)_class_resolveMethod

_class_resolveMethod函數(shù)

_class_resolveMethod函數(shù).png

函數(shù)內(nèi)部會(huì)根據(jù)是元類還是類平委,并且類方法和對(duì)象方法的動(dòng)態(tài)方法解析是調(diào)用不同的函數(shù):
動(dòng)態(tài)解析對(duì)象方法時(shí)奈虾,會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel方法。
動(dòng)態(tài)解析類方法時(shí)廉赔,會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel方法肉微。

動(dòng)態(tài)解析方法之后,會(huì)將triedResolver = YES;那么下次就不會(huì)在進(jìn)行動(dòng)態(tài)解析階段了蜡塌,之后會(huì)回到消息發(fā)送階段碉纳,重新執(zhí)行retry,重新對(duì)方法查找一遍馏艾。

動(dòng)態(tài)方法解析流程圖.png

我們可以利用動(dòng)態(tài)方法解析來(lái)動(dòng)態(tài)的添加方法劳曹。我們將MPerson類中的test方法實(shí)現(xiàn)注釋掉,用other方法的實(shí)現(xiàn)來(lái)替代test方法實(shí)現(xiàn):
動(dòng)態(tài)添加方法.png

從圖中的可以看到琅摩,我們注釋掉test方法實(shí)現(xiàn)后系統(tǒng)已經(jīng)報(bào)出了警告铁孵,下面我們測(cè)試一下代碼:
測(cè)試動(dòng)態(tài)添加方法.png

當(dāng)調(diào)用MPersontest方法時(shí),打印了[MPerson other]房资。動(dòng)態(tài)添加方法成功蜕劝。

這里需要注意class_addMethod函數(shù)用來(lái)向具有給定名稱和實(shí)現(xiàn)的類添加新方法,class_addMethod將添加一個(gè)方法實(shí)現(xiàn)的覆蓋,但是不會(huì)替換已有的實(shí)現(xiàn)岖沛。也就是說如果上述代碼中已經(jīng)實(shí)現(xiàn)了-(void)test方法暑始,則不會(huì)再動(dòng)態(tài)添加方法。

3婴削、消息轉(zhuǎn)發(fā)階段

如果上面兩個(gè)階段都失敗的話廊镜,就會(huì)來(lái)到第三階段:消息轉(zhuǎn)發(fā)階段。
由于OC中消息機(jī)制并不是開源的馆蠕,這里就直接將消息轉(zhuǎn)發(fā)的原理告訴給大家了期升。

進(jìn)入消息轉(zhuǎn)發(fā)階段后,就會(huì)判斷是否指定了其它對(duì)象來(lái)執(zhí)行方法互躬。具體查看當(dāng)前類是否實(shí)現(xiàn)了forwardingTargetForSelector函數(shù)播赁,如果返回值不為空,那么說明指定了轉(zhuǎn)發(fā)目標(biāo)吼渡,那么就會(huì)讓轉(zhuǎn)發(fā)目標(biāo)處理消息容为。

如果forwardingTargetForSelector函數(shù)返回為nil,沒有指定轉(zhuǎn)發(fā)目標(biāo)寺酪,就會(huì)調(diào)用methodSignatureForSelector方法坎背,用來(lái)返回一個(gè)方法簽名,這也是跳轉(zhuǎn)方法的最后機(jī)會(huì)寄雀。

如果methodSignatureForSelector方法返回正確的方法簽名就會(huì)調(diào)用forwardInvocation方法得滤,forwardInvocation方法內(nèi)提供一個(gè)NSInvocation類型的參數(shù),NSInvocation封裝了一個(gè)方法的調(diào)用盒犹,包括方法的調(diào)用者懂更,方法名,以及方法的參數(shù)急膀。在forwardInvocation函數(shù)內(nèi)修改方法調(diào)用對(duì)象即可沮协。

如果methodSignatureForSelector返回的為nil,就會(huì)來(lái)到doseNotRecognizeSelector:方法內(nèi)部卓嫂,程序crash報(bào)出經(jīng)典的錯(cuò)誤unrecognized selector sent to instance慷暂。

消息轉(zhuǎn)發(fā).png

至此,OC消息機(jī)制的分析就告一段落晨雳,OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用行瑞,給方法調(diào)用者(receiver)發(fā)送一條消息(selector方法名)。方法調(diào)用過程包括三個(gè)階段:消息發(fā)送悍募、動(dòng)態(tài)方法解析蘑辑、消息轉(zhuǎn)發(fā)。

更多技術(shù)知識(shí)請(qǐng)關(guān)注公眾號(hào)
iOS進(jìn)階


iOS進(jìn)階.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坠宴,一起剝皮案震驚了整個(gè)濱河市洋魂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖副砍,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衔肢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡豁翎,警方通過查閱死者的電腦和手機(jī)角骤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)心剥,“玉大人邦尊,你說我怎么就攤上這事∮派眨” “怎么了蝉揍?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)畦娄。 經(jīng)常有香客問我又沾,道長(zhǎng),這世上最難降的妖魔是什么熙卡? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任杖刷,我火速辦了婚禮,結(jié)果婚禮上驳癌,老公的妹妹穿的比我還像新娘滑燃。我一直安慰自己,他們只是感情好颓鲜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布不瓶。 她就那樣靜靜地躺著,像睡著了一般灾杰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熙参,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天艳吠,我揣著相機(jī)與錄音,去河邊找鬼孽椰。 笑死昭娩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的黍匾。 我是一名探鬼主播栏渺,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锐涯!你這毒婦竟也來(lái)了磕诊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霎终,沒想到半個(gè)月后滞磺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莱褒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年击困,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片广凸。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阅茶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谅海,到底是詐尸還是另有隱情脸哀,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布胁赢,位于F島的核電站企蹭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏智末。R本人自食惡果不足惜谅摄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望系馆。 院中可真熱鬧送漠,春花似錦、人聲如沸由蘑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尼酿。三九已至爷狈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裳擎,已是汗流浹背涎永。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹿响,地道東北人羡微。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惶我,于是被迫代替她去往敵國(guó)和親妈倔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 方法調(diào)用的本質(zhì) Runtime-Demo 本文我們探尋方法調(diào)用的本質(zhì)绸贡,首先通過一段代碼盯蝴,將方法調(diào)用代碼轉(zhuǎn)為c++代...
    二斤寂寞閱讀 450評(píng)論 0 1
  • 如果想了解Runtime的實(shí)際應(yīng)用請(qǐng)看Runtime全面剖析之簡(jiǎn)單使用 一:Runtime簡(jiǎn)介二: Runtime...
    iYeso閱讀 806評(píng)論 0 2
  • 方法調(diào)用的本質(zhì) 本文我們探尋方法調(diào)用的本質(zhì)毅哗,首先通過一段代碼,將方法調(diào)用代碼轉(zhuǎn)為c++代碼查看方法調(diào)用的本質(zhì)是什么...
    xx_cc閱讀 6,365評(píng)論 6 28
  • 仰天大笑出門去,我輩豈是蓬蒿人松忍。 踏足遠(yuǎn)方蒸殿,遙遙60華里,不禁會(huì)讓我們?cè)诤L(fēng)與激情的碰撞中有些想松...
    清歡1223閱讀 308評(píng)論 0 4