下图是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:查看堆栈