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

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

??在OC端,所有的方法調用,在編譯的時候都轉成了消息的轉發objc_msgSend或者objc_msgSendSuper。那么問題來了,objc_msgSend是怎么找到方法并調用的能??

帶著這個問題,我們找到objc-818.2的源碼,全局搜索一下,經過一番查找發現它的實現是在objc-msg-arm64.s匯編文件中,從ENTRY位置入手。

objc_msgSend實現入口

一、方法快速查找流程

匯編imp快速查找流程:

  1. cmp p0, #0 // 首先是查看消息 receiver 接收者是否存在,如果不存在,再判斷是否支持SUPPORT_TAGGED_POINTERS,支持的話就跳到LNilOrTagged執行(),不支持就跳到LReturnZero執行
  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或者調用objc_msgSend_uncached

  1. mov x15 , x16 // 保存class

    • 調用函數LLookupStart\Function,根據架構選型,arm64的真機是走CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_16
  2. ldr p11, [x16, #CACHE] // 獲取到cache_t存在p11寄存器

    • CACHE 是在類結構體中的偏移 是 (2 * SIZEOF_POINTER) = 16字節,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 // 因為arm64真機的buckets是存在低48位,所以p11 (也就是cache_t) & 掩碼 (#0x0000ffffffffffff) = buckets
    • buckets是存bucket_t結構體的一段連續的內存空間,但是不是數組
  4. eor p12, p1, p1, LSR #7

    • p1存的是_cmd,LSR表示:右移7位,eor表示:異或
      代碼意思就是: p12 = p1 ^ (p1 >> 7) ==> p12 = _cmd ^ (_cmd >> 7),意思就是對sel進行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,轉化一下就是sel的hash值 & mask == (_cmd ^ (_cmd >> 7)) & mask 存在p12寄存器中,其實最終就會得到一個index下標在p12中
  6. add p13, p10, p12, LSL #(1+PTRSHIFT)

    • 此時p10存 buckets(這個其實是那個連續內存的首地址,但是不是數組),p12是index下標,在__LP64__和arm64中PTRSHIFT = 3。
    • 代碼的意思是:p13 = buckets + (index << (1+PTRSHIFT)) ==> buckets + (index << 4) 相當于首地址加上一個下標n(可以用數組的首地址加一個下標n來理解),就是指向buckets容器中的一個內存塊。
  7. 進入遍歷do.....while循環
    7.1 1: ldp p17, p9, [x13], #-BUCKET_SIZE

    • BUCKET_SIZE是一個bucket的大小,x13指向了中間的某一個bucket,-BUCKET_SIZE,就是向前挪了一個位置,{imp, sel} = *bucket--,讀取imp到p17和sel到p9寄存器中

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

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

    7.3 2: CacheHit \Mode

    • 如果p9的sel與 p1的_cmd相等,也就是找到imp,那就調取CacheHit函數

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

    • 如果p9 == 0,也就是sel == 0,則跳轉到 MissLabelDynamic函數,這個形參函數傳進來的值是__objc_msgSend_unCache。否則如果p13 > p10,也就是往前遍歷,如果還大于首地址,則繼續跳到 \color{#ff0000}{ 1 } ,繼續bucket--,繼續找sel和_cmd比較。
  8. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))

    • 如果上面的循環沒有找到,也就是前一半遍歷沒有找到,則定位到后一半繼續遍歷。p10存 buckets,即首地址,p13 = buckets + (mask << 1+PTRSHIFT),p13定位到最后一個bucket_t
  9. add p12, p10, p12, LSL #(1+PTRSHIFT)

    • 在前一半遍歷的時候,p12存的一個index位置的bucket_t,所以后一半遍歷的時候,以p12往后一個作為起始點,以first_probed表示,防止重復遍歷前面的部分。
  10. 4: ldp p17, p9, [x13], #-BUCKET_SIZE

    • p13已經定位到最后一個,然后循環取出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

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

image.png
image.png

小結:

??簡單總結一下,方法快速查找流程是用匯編實現的,其原因個人認為:
其一、匯編實現能夠直接操作寄存器快速,而這部分是調用最多的更能提高速度。
其二、是為了應對不同的調用轉換,因為所有的方法最后都轉化成objc_msgSend消息,其參數數量,參數類型,返回類型都不一樣,使用C、C++、Objective-C是不能做到,就算能做那也要寫龐大的代碼量才能實現,就談不上快速查找了,而使用匯編加上類型轉換,就可以實現了一套簡單的代碼完成所有的調用類型。

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

二、方法慢速查找初探

??我們注意到前面第7.4步,如果找遍了所有的buckets都沒有找到imp,會調用MissLabelDynamic,在第4步調起CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached的時候傳進來的值是__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

會調用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,這個就是方法的慢速查找流程,進入C/C++的方法查找流程和轉發。

小結:

??方法的慢速查找在另一個篇章iOS底層探索--方法慢速查找進行詳細講解,這里是為了能了解到底層是如何從匯編的快速查找無縫銜接到漫長的C/C++查找的。
??匯編的代碼分析是一個枯燥的過程,需要耐心,耐心,耐心,結合源碼的底層數據結構才能更好的理解,讀者可結合思路,自己自己的推敲一遍,你會有一種神清氣爽的感覺。
微風拂面,都仿佛對面走來的女孩在向你示愛!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容