??在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
位置入手治筒。
一屉栓、方法快速查找流程
匯編imp快速查找流程:
cmp p0, #0
// 首先是查看消息 receiver 接收者是否存在,如果不存在耸袜,再判斷是否支持SUPPORT_TAGGED_POINTERS
,支持的話就跳到LNilOrTagged
執(zhí)行()牲平,不支持就跳到LReturnZero
執(zhí)行
ldr p13, [x0]
// p13拿到isa
GetClassFromIsa_p16 p13, 1, x0
// 通過isa拿到class放入p16寄存器
GetClassFromIsa_p16 里面的ExtractISA
拿到class
.macro ExtractISA
and1, #ISA_MASK // $0 = isa & ISA_MASK = class
.endmacro
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
LGetIsaDone
開始calls IMP或者調(diào)用objc_msgSend_uncached
mov x15 , x16
// 保存class
- 調(diào)用函數(shù)LLookupStart\Function堤框,根據(jù)架構(gòu)選型,arm64的真機(jī)是走
CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_16
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之后
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ù)組
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寄存器中
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中
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)存塊建邓。
進(jìn)入遍歷do.....while循環(huán)
7.11: 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罐氨,則跳到
處執(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ù)跳到
,繼續(xù)bucket--畔塔,繼續(xù)找sel和_cmd比較潭辈。
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
- 如果上面的循環(huán)沒有找到鸯屿,也就是前一半遍歷沒有找到,則定位到后一半繼續(xù)遍歷把敢。p10存 buckets寄摆,即首地址,p13 = buckets + (mask << 1+PTRSHIFT)修赞,p13定位到最后一個(gè)bucket_t
add p12, p10, p12, LSL #(1+PTRSHIFT)
- 在前一半遍歷的時(shí)候婶恼,p12存的一個(gè)index位置的bucket_t,所以后一半遍歷的時(shí)候柏副,以p12往后一個(gè)作為起始點(diǎn)勾邦,以first_probed表示,防止重復(fù)遍歷前面的部分搓扯。
4: ldp p17, p9, [x13], #-BUCKET_SIZE
- p13已經(jīng)定位到最后一個(gè)检痰,然后循環(huán)取出bucket_t {imp, sel}存在p17(imp),和p9(sel)中锨推。
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)遍歷换可。
小結(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诳邸!