objc_msgSend 分析

在OC中,方法本質(zhì)上又是什么枯夜?我們調(diào)用一個方法的時候究竟發(fā)生了什么挺智?

方法的本質(zhì)

我們新建一個項目祷愉,在main.m中實現(xiàn)入下代碼。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JKPerson *person = [[JKPerson alloc] init];
        [person saySomething];
    }
    return 0;
}

通過clang來編譯這個main.m文件赦颇。

clang -rewrite-objc main.m

執(zhí)行完這條命令后我們會發(fā)現(xiàn)二鳄,在當前mian.m所在的文件目錄下生成了一個新的main.cpp文件。
在main.cpp文件的最底部媒怯,我們發(fā)現(xiàn)我們main.m中main函數(shù)中的代碼被編譯成了如下形式订讼。

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        JKPerson *person = ((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JKPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
    }
    return 0;
}

這段對象調(diào)用方法的代碼

[person saySomething];

被編譯成了如下形式

((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));

也就是說我們的OC方法其本質(zhì)上就是通過調(diào)用objc_msgSend函數(shù)來發(fā)送消息。
接下來我們來看看在objc_msgSend中究竟做了什么事情扇苞。

objc_msgSend

我們通過給objc_msgSend下符號斷點得知objc_msgSend函數(shù)在我們的libobjc.A.dylib中欺殿。
接下來我們在libobjc.A.dylib中來查看我們的objc_msgSend源碼寄纵。

我們以objc-msg-arm64.s為研究對象。

我們發(fā)現(xiàn)objc_msgSend使用匯編來實現(xiàn)的脖苏,為什么要用匯編來實現(xiàn)呢程拭?有以下幾點原因

匯編更加容易被機器識別,效率更高棍潘。
C語言中不可以通過一個函數(shù)來保留未知的參數(shù)并且跳轉(zhuǎn)到任意的函數(shù)指針恃鞋。C語言沒有滿足這些事情的必要特性。

在objc_msgSend中摘取其中關(guān)鍵代碼如下

    ENTRY _objc_msgSend
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    CacheLookup NORMAL 
    END_ENTRY _objc_msgSend

我們可以看到在獲取到Isa之后我們開啟了方法的緩存查找流程

LGetIsaDone:
    CacheLookup NORMAL 

查找緩存

我們摘取CacheLookup關(guān)鍵代碼如下亦歉。

.macro CacheLookup
    
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
    add p12, p12, w11, UXTW #(1+PTRSHIFT)
                                // p12 = buckets + (mask << 1+PTRSHIFT)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // double wrap
    JumpMiss $0
    
.endmacro

我們可以看出其中有兩個比較重要的恤浪,一個是CacheHit命中緩存,這個時候緩存命中了之后直接返回imp
另一個是CheckMiss,我們來看看CheckMiss做了什么

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

我們跟隨__objc_msgSend_uncached和__objc_msgLookup_uncached流程繼續(xù)往下看

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
肴楷。
    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached

我們可以看到他們都調(diào)用了一個叫MethodTableLookup的東西资锰。
摘取其中關(guān)鍵代碼如下。

.macro MethodTableLookup
    
    bl  __class_lookupMethodAndLoadCache3

.endmacro

流程圖:


image.png
image.png

至此我們關(guān)于objc_msgSend的匯編部分結(jié)束了阶祭,接下來將進入C/C++的查找流程。我們將在下篇文章中介紹objc_msgSend的慢速查找流程直秆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末濒募,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子圾结,更是在濱河造成了極大的恐慌瑰剃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筝野,死亡現(xiàn)場離奇詭異晌姚,居然都是意外死亡,警方通過查閱死者的電腦和手機歇竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門挥唠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人焕议,你說我怎么就攤上這事宝磨。” “怎么了盅安?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵唤锉,是天一觀的道長。 經(jīng)常有香客問我别瞭,道長窿祥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任蝙寨,我火速辦了婚禮晒衩,結(jié)果婚禮上嗤瞎,老公的妹妹穿的比我還像新娘。我一直安慰自己浸遗,他們只是感情好猫胁,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跛锌,像睡著了一般弃秆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上髓帽,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天菠赚,我揣著相機與錄音,去河邊找鬼郑藏。 笑死衡查,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的必盖。 我是一名探鬼主播拌牲,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼歌粥!你這毒婦竟也來了塌忽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤失驶,失蹤者是張志新(化名)和其女友劉穎土居,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嬉探,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡擦耀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涩堤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眷蜓。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胎围,靈堂內(nèi)的尸體忽然破棺而出账磺,到底是詐尸還是另有隱情,我是刑警寧澤痊远,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布垮抗,位于F島的核電站,受9級特大地震影響碧聪,放射性物質(zhì)發(fā)生泄漏冒版。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一逞姿、第九天 我趴在偏房一處隱蔽的房頂上張望辞嗡。 院中可真熱鬧捆等,春花似錦、人聲如沸续室。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挺狰。三九已至明郭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丰泊,已是汗流浹背薯定。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞳购,地道東北人话侄。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像学赛,于是被迫代替她去往敵國和親年堆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345