下圖是cache的讀寫流程,當咱們讀取cache的時候,須要調用cache_getImp
本文的重點在objc_msgSend
,是發起讀取cache
的地方.html
編譯時
顧名思義就是正在編譯的時候,就是編譯器幫你把源代碼翻譯成機器能識別的代碼。(固然這只是一意義上這麼說,實際上可能只是翻譯成某個中間狀態的語音。)好比靜態類型檢查之類的。c++
運行時
顧名思義就是在代碼運行起來後,被裝載到內存中。(當你的代碼保存在磁盤中,沒有寫入內存以前都是「死」代碼,只有寫入內存中才變成「活」代碼。並且運行時類型檢查與編譯時類型檢查(或者靜態類型檢查)是不同的,不是簡單的掃描代碼,而是在內存中作了一些操做和判斷。 詳見apple runtimegit
runtime
調用的三種方式:github
@interface MLTeacher : NSObject
- (void)sayHello;
@end
@implementation MLTeacher
- (void)sayHello{
NSLog(@"666 %s",__func__);
}
@end
@interface MLPerson : MLTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation MLPerson
- (void)sayNB{
NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MLPerson *person = [MLPerson alloc];
MLTeacher *teach = [MLTeacher alloc];
[person sayNB];
[person sayHello];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
經過命令clang -rewrite-objc main.m -o main.cpp
生成cpp
文件緩存
能夠發現sayNB
之類的方法調用都是經過objc_msgSend
來實現的。markdown
那麼咱們是否可以直接調用objc_msgSend
方法呢?咱們調用的時候可能會編譯失敗,須要將Build Settings -> Enable Strict Checking of objc_msgSend Calls
設置成爲 NO
, 以下圖 引入頭文件#import <objc/message.h>
代碼以下app
MLPerson *person = [MLPerson alloc];
objc_msgSend(person, @selector(sayNB));
複製代碼
若是咱們重載方法,調用super
,cpp
的實現會是什麼樣呢?ide
static void _I_MLPerson_sayHello(MLPerson * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MLPerson"))}, sel_registerName("sayHello"));
}
複製代碼
發現cpp
的實現是經過調用objc_msgSendSuper
實現的. 經過上述的分析,以及對runtime的體驗,方法的本質就是objc_msgSend
.oop
當在源碼中全局搜索objc_msgSend
的時候,發現objc_msgSend
的實現是彙編 以下是在arm64
中objc_msgSend
的彙編實現 此時有個疑問是爲何使用匯編實現的,而不是經過c++的代碼實現的?ui
line 54:cmp p0 #0 //p0 是receiver的地址,對p0 與 0 比較,若是爲nil直接返回 line 56 或者 line 58
line 60: 將[x0]的內容讀取到p13, [x0]是 isa
line 61: p16 賦值爲 isa,下圖爲GetClassFromIsa_p16
源碼
實際就是指針地址與ISA_MASK
, 獲取到isa
的地址,獲取isa
的地址是要獲取cache
, 先從cache
中找到IMP
. line 64: CacheLookup
1. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
2.
3. mov x15, x16 // stash the original isa, x16:isa
4. LLookupStart\Function:
5. // p1 = SEL, p16 = isa
6. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
7. ldr p10, [x16, #CACHE] // p10 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
8. lsr p11, p10, #48 // p11 = mask
9. and p10, p10, #0xffffffffffff // p10 = buckets
10. and w12, w1, w11 // x12 = _cmd & mask
11. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
12. ldr p11, [x16, #CACHE] // p11 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
13. #if CONFIG_USE_PREOPT_CACHES
14. #if __has_feature(ptrauth_calls)
15. tbnz p11, #0, LLookupPreopt\Function
16. and p10, p11, #0x0000ffffffffffff // p10 = buckets
17. #else
18. and p10, p11, #0x0000fffffffffffe // p10 = buckets
19. tbnz p11, #0, LLookupPreopt\Function
20. #endif
21. eor p12, p1, p1, LSR #7
22. and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
23. #else
24. and p10, p11, #0x0000ffffffffffff // p10 = buckets
25. and p12, p1, p11, LSR #48 // x12 = _cmd & mask
26. #endif // CONFIG_USE_PREOPT_CACHES
27. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
28. ldr p11, [x16, #CACHE] // p11 = mask|buckets
29. and p10, p11, #~0xf // p10 = buckets
30. and p11, p11, #0xf // p11 = maskShift
31. mov p12, #0xffff
32. lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
33. and p12, p1, p11 // x12 = _cmd & mask
34. #else
35. #error Unsupported cache mask storage for ARM64.
36. #endif
37.
38. add p13, p10, p12, LSL #(1+PTRSHIFT)
39. // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
40.
41. // do {
42. 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
43. cmp p9, p1 // if (sel != _cmd) {
44. b.ne 3f // scan more
45. // } else {
46. 2: CacheHit \Mode // hit: call or return imp
47. // }
48. 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
49. cmp p13, p10 // } while (bucket >= buckets)
50. b.hs 1b
51.
52. // wrap-around:
53. // p10 = first bucket
54. // p11 = mask (and maybe other bits on LP64)
55. // p12 = _cmd & mask
56. //
57. // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
58. // So stop when we circle back to the first probed bucket
59. // rather than when hitting the first bucket again.
60. //
61. // Note that we might probe the initial bucket twice
62. // when the first probed slot is the last entry.
63.
64.
65. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
66. add p13, p10, w11, UXTW #(1+PTRSHIFT)
67. // p13 = buckets + (mask << 1+PTRSHIFT)
68. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
69. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
70. // p13 = buckets + (mask << 1+PTRSHIFT)
71. // see comment about maskZeroBits
72. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
73. add p13, p10, p11, LSL #(1+PTRSHIFT)
74. // p13 = buckets + (mask << 1+PTRSHIFT)
75. #else
76. #error Unsupported cache mask storage for ARM64.
77. #endif
78. add p12, p10, p12, LSL #(1+PTRSHIFT)
79. // p12 = first probed bucket
80.
81. // do {
82. 4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
83. cmp p9, p1 // if (sel == _cmd)
84. b.eq 2b // goto hit
85. cmp p9, #0 // } while (sel != 0 &&
86. ccmp p13, p12, #0, ne // bucket > first_probed)
87. b.hi 4b
88.
89. LLookupEnd\Function:
90. LLookupRecover\Function:
91. b \MissLabelDynamic
92.
93. #if CONFIG_USE_PREOPT_CACHES
94. #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
95. #error config unsupported
96. #endif
97. LLookupPreopt\Function:
98. #if __has_feature(ptrauth_calls)
99. and p10, p11, #0x007ffffffffffffe // p10 = buckets
100. autdb x10, x16 // auth as early as possible
101. #endif
102.
103. // x12 = (_cmd - first_shared_cache_sel)
104. adrp x9, _MagicSelRef@PAGE
105. ldr p9, [x9, _MagicSelRef@PAGEOFF]
106. sub p12, p1, p9
107.
108. // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
109. #if __has_feature(ptrauth_calls)
110. // bits 63..60 of x11 are the number of bits in hash_mask
111. // bits 59..55 of x11 is hash_shift
112.
113. lsr x17, x11, #55 // w17 = (hash_shift, ...)
114. lsr w9, w12, w17 // >>= shift
115.
116. lsr x17, x11, #60 // w17 = mask_bits
117. mov x11, #0x7fff
118. lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
119. and x9, x9, x11 // &= mask
120. #else
121. // bits 63..53 of x11 is hash_mask
122. // bits 52..48 of x11 is hash_shift
123. lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
124. lsr w9, w12, w17 // >>= shift
125. and x9, x9, x11, LSR #53 // &= mask
126. #endif
127.
128. ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
129. cmp x12, w17, uxtw
130.
131. .if \Mode == GETIMP
132. b.ne \MissLabelConstant // cache miss
133. sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
134. SignAsImp x0
135. ret
136. .else
137. b.ne 5f // cache miss
138. sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
139. .if \Mode == NORMAL
140. br x17
141. .elseif \Mode == LOOKUP
142. orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
143. SignAsImp x17
144. ret
145. .else
146. .abort unhandled mode \Mode
147. .endif
148.
149. 5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
150. add x16, x16, x9 // compute the fallback isa
151. b LLookupStart\Function // lookup again with a new isa
152. .endif
153. #endif // CONFIG_USE_PREOPT_CACHES
154.
155. .endmacro
複製代碼
上述彙編的流程以下:
x15, x16
將 isa
存儲到x15
ldr p11, [x16, #CACHE]
, 其中CACHE = 16
, p11 = x16+16
, 由class
結構可知, p11
爲cache_t
的首地址and p10, p11, #0x0000fffffffffffe
, p10 = p11 & 00x0000fffffffffffe
, 0x0000fffffffffffe
爲buckets
的掩碼, 獲得p10 = buckets 首地址
.p11, #0, LLookupPreopt\Function
, 不爲0就跳轉到LLookupPreopt
add p13, p10, p12, LSL #(1+PTRSHIFT)
, p12
爲當前sel, imp
的index
, p13 = buckets + (_cmd & mask) << (1 + PTRSHIFT)
. PTRSHIFT
是 3
. 右移4位的緣由,是要知道buckets
內存平移須要平移幾個單位. 即buckets[i]
. p13
爲當前要查找的bucket
. 1:ldp p17, p9, [x13], #-BUCKET_SIZE
, 查找到當前位置存不存在bucket
. 若是找到就跳轉到Cachehit
,緩存命中,不然的話,就循環查找. 若是找到,就callimp
.從上述源碼中咱們能夠大概瞭解到objc_msgSend
的流程爲以下:
receiver
獲取到isa
isa
獲取cache
cache
(包含buckets & mask
), 經過buckets
的掩碼獲得buckets
.mask
掩碼獲得mask
(mask
爲當前buckets
的capacipty -1
).sel imp
進行hash
, 獲得第一次查找的index
buckets + index
整個緩存中的第幾個bucket
sel imp
與傳進來的sel == _cmd
進行比較, 若是相等,緩存命中,調用imp
buckets + index --
,向前查找,跳轉到step 7
buckets
遍歷結束,都沒有找到, 跳轉到objc_msgSend_uncached
objc_msgSend_uncached
會在後續的文章更新...bt:查看堆棧