OC 底層探索 09、objc_msgSend 流程簡析1
本文對 Runtime 進行簡單介紹 和對 objc_msgSend 的發訊息流程中的快取查詢進行探索。
我們知道類結構中包含了很多資訊:isa superclass cache bits,cache 中快取了我們呼叫的方法,具體流程見OC底層探索07. 但是方法具體時間什麼時候快取的,需要繼續探究。
原始碼 objc_cache.mm 檔案,Method cache locking 中,我們可以看到在寫入之前還有一個讀的過程
--> objc_msgSend* / cache_getImp:
看到 objc_msgSend 就不免想到 runtime,我們先簡單介紹下 runtime 是什麼。
一、Runtime 簡介
Runtime 是一個為我們OC語言開發中提供動態特性的一個庫。
官方文件:Objective-C Runtime
/Objective-C Runtime Programming Guide
《The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system
OC 語言將盡可能多的決策從編譯時和連結時延遲到執行時。只要有可能,它就動態地做事情。這意味著該語言不僅需要一個編譯器,還需要一個執行時系統來執行編譯後的程式碼。執行時系統作為Objective-C語言的一種作業系統;它使語言起作用。
提取資訊點:一個提供動態特性的庫,實現執行時作用。
1、Runtime 結構和呼叫方式:
complier: 編譯 --> runtime 底層所呼叫的並非我們寫的程式碼,它進過了 complier 進行編譯、優化。 -->LLVM
tip:編譯時、連結時,一句話簡介{
編譯時:
1、對我們的程式碼進行語法詞法等分析,並給出waring、error等提示。類似一個掃描過程,將程式碼掃一遍;
2、將編寫的高階語言(C C++ OC等)編譯成機器識別的機器語言(彙編、機器語言01等),編譯得到相應的二進位制目標檔案。
編譯時並沒有進行載入分配記憶體等操作。
連結時:
將編譯得到的二進位制檔案和庫函式等進行連線。
執行時:
程式碼已被裝載到記憶體中,已執行起來了。
}
2、探索
通過clang 將 main.m 檔案編譯成 mian.cpp. mian函式編譯結果如下:
id objc_msgSend(id self, SEL _cmd, ...) :
訊息接受者 self;訊息體 SEL. 通過查詢 objc_msgSend() 原始碼,我們可以發現它是使用匯編實現的,這裡暫時先不探究,繼續當前操作。
sel_registerName("helloObj1"):
註冊一個方法。例: 上層的操作 @selector() /NSSelectorFromString() 都是 SEL型別.
直接使用 API -objc_msgSend()進行方法呼叫
對程式碼進行修改:
報錯如下:
修改工程配置:Building Setting --> Preprocessing --> 將 objc_msgSend call 嚴格檢查改為 NO.
執行結果如下,通過 objc_msgSend() 正常呼叫了方法 'helloObj3':
OC 方法呼叫 --> objc_msgSend() --> sel(方法編號) --> imp(函式指標地址) --> 函式.
sel 如何找到 imp 呢?
二、objc_msgSend 流程探索- cache
從上面的操作,可知方法呼叫的本質是 傳送訊息,下面進行 傳送訊息流程的探究。
全域性查詢 objc_msgSend(),是通過彙編實現的
使用匯編的原因:
1、快速,方法的查詢操作是很頻繁的,彙編是相對底層的語言更易被機器識別,節省中間的一些編譯過程。
2、語言的動態特性,C/C++ 來編寫實現的話更偏向於靜態,雖然也可實現會更麻煩且慢。
下面我們通過原始碼 註釋 和 網路 對訊息查詢流程進行探索。
objc_msgSend 流程分析
訊息接收者 和 sel:
訊息接受者 --> 物件 --> isa --> 方法(類/元類) --> cache_t --> methodliss(bits中)
1、主要流程原始碼 CacheLookup:
1 .macro CacheLookup 2 // 3 // Restart protocol: 4 // 5 // As soon as we're past the LLookupStart$1 label we may have loaded 6 // an invalid cache pointer or mask. 7 // 8 // When task_restartable_ranges_synchronize() is called, 9 // (or when a signal hits us) before we're past LLookupEnd$1, 10 // then our PC will be reset to LLookupRecover$1 which forcefully 11 // jumps to the cache-miss codepath which have the following 12 // requirements: 13 // 14 // GETIMP: 15 // The cache-miss is just returning NULL (setting x0 to 0) 16 // 17 // NORMAL and LOOKUP: 18 // - x0 contains the receiver 19 // - x1 contains the selector 20 // - x16 contains the isa 21 // - other registers are set as per calling conventions 22 // 23 LLookupStart$1: 24 25 // p1 = SEL, p16 = isa 26 ldr p11, [x16, #CACHE] // p11 = mask|buckets 27 28 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 29 and p10, p11, #0x0000ffffffffffff // p10 = buckets 30 and p12, p1, p11, LSR #48 // x12 = _cmd & mask 31 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 32 and p10, p11, #~0xf // p10 = buckets 33 and p11, p11, #0xf // p11 = maskShift 34 mov p12, #0xffff 35 lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 36 and p12, p1, p11 // x12 = _cmd & mask 37 #else 38 #error Unsupported cache mask storage for ARM64. 39 #endif 40 41 42 add p12, p10, p12, LSL #(1+PTRSHIFT) 43 // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) // PTRSHIFT=3 44 45 ldp p17, p9, [x12] // {imp, sel} = *bucket 46 1: cmp p9, p1 // if (bucket->sel != _cmd) 47 b.ne 2f // scan more 48 CacheHit $0 // call or return imp 49 50 2: // not hit: p12 = not-hit bucket 51 CheckMiss $0 // miss if bucket->sel == 0 52 cmp p12, p10 // wrap if bucket == buckets 53 b.eq 3f 54 ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket 55 b 1b // loop 56 57 3: // wrap: p12 = first bucket, w11 = mask 58 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 59 add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) 60 // p12 = buckets + (mask << 1+PTRSHIFT) 61 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 62 add p12, p12, p11, LSL #(1+PTRSHIFT) 63 // p12 = buckets + (mask << 1+PTRSHIFT) 64 #else 65 #error Unsupported cache mask storage for ARM64. 66 #endif 67 68 // Clone scanning loop to miss instead of hang when cache is corrupt. 69 // The slow path may detect any corruption and halt later. 70 71 ldp p17, p9, [x12] // {imp, sel} = *bucket 72 1: cmp p9, p1 // if (bucket->sel != _cmd) 73 b.ne 2f // scan more 74 CacheHit $0 // call or return imp 75 76 2: // not hit: p12 = not-hit bucket 77 CheckMiss $0 // miss if bucket->sel == 0 78 cmp p12, p10 // wrap if bucket == buckets 79 b.eq 3f 80 ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket 81 b 1b // loop 82 83 LLookupEnd$1: 84 LLookupRecover$1: 85 3: // double wrap 86 JumpMiss $0 87 88 .endmacro