iOS底層探索--方法快速查找(匯編)流程&慢速查找初探

iOS底層探索--方法慢速查找

??在OC端,所有的方法調(diào)用绘闷,在編譯的時(shí)候都轉(zhuǎn)成了消息的轉(zhuǎn)發(fā)objc_msgSend或者objc_msgSendSuper诗力。那么問題來了鲫竞,objc_msgSend是怎么找到方法并調(diào)用的能叁怪?蕊连?

帶著這個(gè)問題,我們找到objc-818.2的源碼祝闻,全局搜索一下占卧,經(jīng)過一番查找發(fā)現(xiàn)它的實(shí)現(xiàn)是在objc-msg-arm64.s匯編文件中,從ENTRY位置入手治筒。

objc_msgSend實(shí)現(xiàn)入口

一屉栓、方法快速查找流程

匯編imp快速查找流程:

  1. cmp p0, #0 // 首先是查看消息 receiver 接收者是否存在,如果不存在耸袜,再判斷是否支持SUPPORT_TAGGED_POINTERS,支持的話就跳到LNilOrTagged執(zhí)行()牲平,不支持就跳到LReturnZero執(zhí)行
  1. ldr p13, [x0] // p13拿到isa

  2. GetClassFromIsa_p16 p13, 1, x0 // 通過isa拿到class放入p16寄存器
    GetClassFromIsa_p16 里面的 ExtractISA拿到class
    .macro ExtractISA
    and 0,1, #ISA_MASK // $0 = isa & ISA_MASK = class
    .endmacro

  3. CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    LGetIsaDone 開始calls IMP或者調(diào)用objc_msgSend_uncached

  1. mov x15 , x16 // 保存class

    • 調(diào)用函數(shù)LLookupStart\Function堤框,根據(jù)架構(gòu)選型,arm64的真機(jī)是走CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_16
  2. ldr p11, [x16, #CACHE] // 獲取到cache_t存在p11寄存器

    • CACHE 是在類結(jié)構(gòu)體中的偏移 是 (2 * SIZEOF_POINTER) = 16字節(jié),p11 = x16 + 0x10 得到 => cache_t
    • CONFIG_USE_PREOPT_CACHES = 1
    • __has_feature(ptrauth_calls) A12之后蜈抓,也就是iPhone X之后
  3. tbnz p11, #0, LLookupPreopt\Function // 判斷cache_t是否存在

    • and p10, p11, #0x0000ffffffffffff // 因?yàn)閍rm64真機(jī)的buckets是存在低48位启绰,所以p11 (也就是cache_t) & 掩碼 (#0x0000ffffffffffff) = buckets
    • buckets是存bucket_t結(jié)構(gòu)體的一段連續(xù)的內(nèi)存空間,但是不是數(shù)組
  4. eor p12, p1, p1, LSR #7

    • p1存的是_cmd沟使,LSR表示:右移7位委可,eor表示:異或
      代碼意思就是: p12 = p1 ^ (p1 >> 7) ==> p12 = _cmd ^ (_cmd >> 7),意思就是對(duì)sel進(jìn)行hash腊嗡,將哈希之后的值存在p12寄存器中
  5. and p12, p12, p11, LSR #48

    • p11存cache_t着倾,p12存sel的哈希值,cache_t在arm64中低48位是buckets燕少,高16位是mask
    • 代碼的意思是: p12 & (p11 >> 48)卡者,p11 >> 48得到高16位的mask,轉(zhuǎn)化一下就是sel的hash值 & mask == (_cmd ^ (_cmd >> 7)) & mask 存在p12寄存器中客们,其實(shí)最終就會(huì)得到一個(gè)index下標(biāo)在p12中
  6. add p13, p10, p12, LSL #(1+PTRSHIFT)

    • 此時(shí)p10存 buckets(這個(gè)其實(shí)是那個(gè)連續(xù)內(nèi)存的首地址崇决,但是不是數(shù)組),p12是index下標(biāo)底挫,在__LP64__和arm64中PTRSHIFT = 3恒傻。
    • 代碼的意思是:p13 = buckets + (index << (1+PTRSHIFT)) ==> buckets + (index << 4) 相當(dāng)于首地址加上一個(gè)下標(biāo)n(可以用數(shù)組的首地址加一個(gè)下標(biāo)n來理解),就是指向buckets容器中的一個(gè)內(nèi)存塊建邓。
  7. 進(jìn)入遍歷do.....while循環(huán)
    7.1 1: ldp p17, p9, [x13], #-BUCKET_SIZE

    • BUCKET_SIZE是一個(gè)bucket的大小碌冶,x13指向了中間的某一個(gè)bucket,-BUCKET_SIZE涝缝,就是向前挪了一個(gè)位置扑庞,{imp, sel} = *bucket--,讀取imp到p17和sel到p9寄存器中

    7.2 cmp p9, p1
    ? ?b.ne 3f

    • 如果p9(也就是sel)不等于p1(也就是_cmd)拒逮,也就是沒有找到imp罐氨,則跳到\color{#ff0000}{ 3 }處執(zhí)行

    7.3 2: CacheHit \Mode

    • 如果p9的sel與 p1的_cmd相等,也就是找到imp滩援,那就調(diào)取CacheHit函數(shù)

    7.4 3: cbz p9, \MissLabelDynamic
    ???cmp p13, p10
    ???b.hs 1b

    • 如果p9 == 0栅隐,也就是sel == 0,則跳轉(zhuǎn)到 MissLabelDynamic函數(shù)玩徊,這個(gè)形參函數(shù)傳進(jìn)來的值是__objc_msgSend_unCache租悄。否則如果p13 > p10,也就是往前遍歷恩袱,如果還大于首地址泣棋,則繼續(xù)跳到 \color{#ff0000}{ 1 } ,繼續(xù)bucket--畔塔,繼續(xù)找sel和_cmd比較潭辈。
  8. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))

    • 如果上面的循環(huán)沒有找到鸯屿,也就是前一半遍歷沒有找到,則定位到后一半繼續(xù)遍歷把敢。p10存 buckets寄摆,即首地址,p13 = buckets + (mask << 1+PTRSHIFT)修赞,p13定位到最后一個(gè)bucket_t
  9. add p12, p10, p12, LSL #(1+PTRSHIFT)

    • 在前一半遍歷的時(shí)候婶恼,p12存的一個(gè)index位置的bucket_t,所以后一半遍歷的時(shí)候柏副,以p12往后一個(gè)作為起始點(diǎn)勾邦,以first_probed表示,防止重復(fù)遍歷前面的部分搓扯。
  10. 4: ldp p17, p9, [x13], #-BUCKET_SIZE

    • p13已經(jīng)定位到最后一個(gè)检痰,然后循環(huán)取出bucket_t {imp, sel}存在p17(imp),和p9(sel)中锨推。
  11. cmp p9, p1 // if (sel == _cmd)
    b.eq 2b // goto cacheHit
    cmp p9, #0 // } while (sel != 0 &&
    ccmp p13, p12, #0, ne // bucket > first_probed)
    b.hi 4b

    • 然后依次遍歷對(duì)比铅歼,如果sel == _cmd ,則跳到cacheHit執(zhí)行,否則只要sel != 0 且p13與起始位置沒有碰面(bucket > first_probed)繼續(xù)循環(huán)遍歷换可。
7.1可看

image.png
image.png

小結(jié):

??簡單總結(jié)一下椎椰,方法快速查找流程是用匯編實(shí)現(xiàn)的,其原因個(gè)人認(rèn)為:
其一沾鳄、匯編實(shí)現(xiàn)能夠直接操作寄存器快速慨飘,而這部分是調(diào)用最多的更能提高速度。
其二译荞、是為了應(yīng)對(duì)不同的調(diào)用轉(zhuǎn)換瓤的,因?yàn)樗械姆椒ㄗ詈蠖嫁D(zhuǎn)化成objc_msgSend消息,其參數(shù)數(shù)量吞歼,參數(shù)類型圈膏,返回類型都不一樣,使用C篙骡、C++稽坤、Objective-C是不能做到,就算能做那也要寫龐大的代碼量才能實(shí)現(xiàn)糯俗,就談不上快速查找了尿褪,而使用匯編加上類型轉(zhuǎn)換,就可以實(shí)現(xiàn)了一套簡單的代碼完成所有的調(diào)用類型得湘。

獲取到class和isa ---> 通過偏移拿到cache_t--->根據(jù)架構(gòu)與上掩碼得到buckets和mask--->定位index使用do...while遍歷前一半和后一半找sel與_cmd對(duì)比來查找imp--->找到則CacheHit--->找不到MissLabelDynamic杖玲,也就是__objc_msgSend_uncached(漫長的C/C++方法查找)

二、方法慢速查找初探

??我們注意到前面第7.4步忽刽,如果找遍了所有的buckets都沒有找到imp天揖,會(huì)調(diào)用MissLabelDynamic夺欲,在第4步調(diào)起CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached的時(shí)候傳進(jìn)來的值是__objc_msgSend_uncached跪帝。其源碼如下:

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

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

    END_ENTRY __objc_msgSend_uncached

會(huì)調(diào)用MethodTableLookup

.macro MethodTableLookup
    
    SAVE_REGS MSGSEND

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0

    RESTORE_REGS MSGSEND

.endmacro

然后bl _lookUpImpOrForward今膊,這個(gè)就是方法的慢速查找流程,進(jìn)入C/C++的方法查找流程和轉(zhuǎn)發(fā)伞剑。

小結(jié):

??方法的慢速查找在另一個(gè)篇章iOS底層探索--方法慢速查找進(jìn)行詳細(xì)講解斑唬,這里是為了能了解到底層是如何從匯編的快速查找無縫銜接到漫長的C/C++查找的。
??匯編的代碼分析是一個(gè)枯燥的過程黎泣,需要耐心恕刘,耐心,耐心抒倚,結(jié)合源碼的底層數(shù)據(jù)結(jié)構(gòu)才能更好的理解褐着,讀者可結(jié)合思路,自己自己的推敲一遍托呕,你會(huì)有一種神清氣爽的感覺含蓉。
微風(fēng)拂面,都仿佛對(duì)面走來的女孩在向你示愛O罱肌O诳邸!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末着降,一起剝皮案震驚了整個(gè)濱河市差油,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌任洞,老刑警劉巖蓄喇,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異交掏,居然都是意外死亡妆偏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耀销,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楼眷,“玉大人,你說我怎么就攤上這事熊尉」蘖” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵狰住,是天一觀的道長张吉。 經(jīng)常有香客問我,道長催植,這世上最難降的妖魔是什么肮蛹? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任勺择,我火速辦了婚禮,結(jié)果婚禮上伦忠,老公的妹妹穿的比我還像新娘省核。我一直安慰自己,他們只是感情好昆码,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布气忠。 她就那樣靜靜地躺著,像睡著了一般赋咽。 火紅的嫁衣襯著肌膚如雪旧噪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天脓匿,我揣著相機(jī)與錄音淘钟,去河邊找鬼。 笑死陪毡,一個(gè)胖子當(dāng)著我的面吹牛米母,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缤骨,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爱咬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绊起?” 一聲冷哼從身側(cè)響起精拟,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱歪,沒想到半個(gè)月后蜂绎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笋鄙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年师枣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萧落。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡践美,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出找岖,到底是詐尸還是另有隱情陨倡,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布许布,位于F島的核電站兴革,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杂曲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一庶艾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擎勘,春花似錦咱揍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朱转。三九已至蟹地,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藤为,已是汗流浹背怪与。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缅疟,地道東北人分别。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像存淫,于是被迫代替她去往敵國和親耘斩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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