OC底層07:objc_msgSend流程分析

前言

當我們定義一段代碼:

        Test *p = [Test alloc];
        [p test1];

它在底層是如何實現(xiàn)的?通過clang進行編譯,可以找到:

        Test *p = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test1"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test2"));

很明顯,OC在底層是通過objc_msgSend傳遞消息的,第一個參數(shù)是接收對象,第二個參數(shù)是sel_registerName() (ps:sel_registerName()==@selector()==NSSelectorFromString())
那么objc_msgSend在底層又是如何實現(xiàn)的?

源碼解析

因為匯編更快的特性,所以objc_msgSend底層是由匯編實現(xiàn)的胀葱。

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    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:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend

    …………
    END_ENTRY _objc_msgSend
1.檢查接收者為不為空,為空結(jié)束
    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
2.拿到isa
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
3.查找cache中是否有需要的方法
CacheLookup NORMAL, _objc_msgSend

.macro CacheLookup
LLookupStart$1:
    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    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:  // wrap: p12 = first bucket, w11 = mask
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
    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

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0
.endmacro
  1. 地址偏移到cache_t在類中的位置 #define CACHE (2 * __SIZEOF_POINTER__) == 16字節(jié)
ldr p11, [x16, #CACHE]              // p11 = mask|buckets
  1. cache_t左16位為mask,右48位為buckets
    先得到buckets
//與上0x0000ffffffffffff 得到buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets

因為cache_t分析中提到,方法在cache中進行哈希獲取角標進行存儲荧缘。

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}

所以先獲取mask再與上sel

and p12, p1, p11, LSR #48       // x12 = _cmd & mask
  1. buckets地址偏移到對應位置開始查找
add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

PTRSHIFT3,左移4位及*16,因為buckets包含selimp,所以一個就是16字節(jié),+ ((_cmd & mask) << (1+PTRSHIFT))則是地址偏移到對應位置戴而。

  1. 循環(huán)查找
  • 循環(huán)條件:sel != cmd
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
  • 初始條件:當bucket==buckets,及當前bucket為第一個bucket時,跳轉(zhuǎn)到最后一個bucket,逆序查找
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
//mask==總占用-1,mask左移16位,則為總buckets大小
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
  • 如果找到,則返回,沒有找到則進入CheckMiss,及進入慢速查找流程_lookUpImpOrForward
.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

    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

.macro MethodTableLookup
     …………
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward
      …………
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虐呻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痛侍,更是在濱河造成了極大的恐慌惜颇,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翩剪,死亡現(xiàn)場離奇詭異,居然都是意外死亡彩郊,警方通過查閱死者的電腦和手機前弯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秫逝,“玉大人恕出,你說我怎么就攤上這事∥シ” “怎么了浙巫?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刷后。 經(jīng)常有香客問我的畴,道長,這世上最難降的妖魔是什么尝胆? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任丧裁,我火速辦了婚禮,結(jié)果婚禮上含衔,老公的妹妹穿的比我還像新娘煎娇。我一直安慰自己,他們只是感情好贪染,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布缓呛。 她就那樣靜靜地躺著,像睡著了一般杭隙。 火紅的嫁衣襯著肌膚如雪哟绊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天痰憎,我揣著相機與錄音票髓,去河邊找鬼。 笑死信殊,一個胖子當著我的面吹牛炬称,可吹牛的內(nèi)容都是我干的汁果。 我是一名探鬼主播涡拘,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼据德!你這毒婦竟也來了鳄乏?” 一聲冷哼從身側(cè)響起跷车,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橱野,沒想到半個月后朽缴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡水援,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年密强,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜗元。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡或渤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奕扣,到底是詐尸還是另有隱情薪鹦,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布惯豆,位于F島的核電站池磁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏楷兽。R本人自食惡果不足惜地熄,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芯杀。 院中可真熱鬧离斩,春花似錦、人聲如沸瘪匿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棋弥。三九已至核偿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顽染,已是汗流浹背漾岳。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粉寞,地道東北人尼荆。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像唧垦,于是被迫代替她去往敵國和親捅儒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350