源碼層面:
調(diào)用_objc_msgSend 匯編
- 先查找cache_t,沒(méi)有的話找methodlist
- 指向父類,并重復(fù)1
- 如果最后父類都沒(méi)有實(shí)現(xiàn)則走到OC層面的轉(zhuǎn)發(fā)流程
- 如果找到了則將方法放入當(dāng)前接受者類的cache中加入此方法,如果加入的時(shí)候判斷,cache列表的空間即將用完,則清空cache并加倍申請(qǐng)空間,并將方法加入cache中,并調(diào)用此方法
源碼解讀:
- 對(duì)receiver判空
- 空則調(diào)用LNilOrTagged或者LReturnZero,否則3 —> 這就是我們給nil發(fā)送方法不會(huì)出問(wèn)題的原因,因?yàn)橹苯泳头祷亓?/li>
- 將receiver的地址賦值給p13,調(diào)用 GetClassFromIsa_p16
- GetClassFromIsa_p16調(diào)用ExtractISA計(jì)算地址
- 經(jīng)過(guò)運(yùn)算【receiver的地址與上isamask】得到isa地址
- 接著往下走 LGetisaDone(標(biāo)簽),調(diào)用 CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
- CacheLookup往下走到 LLookupStart\Function標(biāo)簽,將isa指針向后移16個(gè)地址,計(jì)算出cache_t的地址,cache_t的地址等于mask|buckets,高16位是mask,低48位是buckets,但是第一位因?yàn)榕c的時(shí)候沒(méi)有用,應(yīng)該是存了某個(gè)標(biāo)志位
- cache_t的地址與上 #0x0000fffffffffffe得到buckets的地址
- 判斷cache_t地址的第0位是否為0,不為0則跳轉(zhuǎn)到LLookupPreopt,否則10
- 將sel右移7位與sel異或,再將cachet_t右移48位得到mask,將兩者相與得到要查找的方法在buckets中的下標(biāo) —> 這里本質(zhì)是利用哈希函數(shù)得到下表 cache_hash
- 從當(dāng)前方法應(yīng)該在buckets中的地址往前開始遍歷buckets,判斷bucket的sel是否和要查找的相同,找到則走cacheHit 13,未找到則往下14 —> 向前遍歷的原因是因?yàn)楣_突的解決方案是下標(biāo)位置-1,如果前面都放滿了則指向buckets的尾部,再往前找位置 cache_next
- 每次判斷sel和要查找的sel相等時(shí),會(huì)做一個(gè)安全檢測(cè)如果bucket中的某個(gè)sel為0了,則調(diào)用__objc_msgSend_uncached。這里的緣由應(yīng)該這么理解,因?yàn)樵赾ache中查找的過(guò)程中發(fā)現(xiàn)了一例臟數(shù)據(jù),則默認(rèn)這一段數(shù)據(jù)不可用了,則直接走了沒(méi)找到的流程。
- cachehint會(huì)調(diào)用TailCallCachedImp,將cache中找到的imp異或上isa得到真實(shí)的imp地址返回。 —> 因?yàn)閷mp存入cache時(shí)編碼了一次 就是用的 imp異或isa,則cache里面存儲(chǔ)的值是異或過(guò)后的值。
- 計(jì)算出當(dāng)前buckets的尾部地址,從此地址往前遍歷到上一次循環(huán)前得到的首地址,避免重復(fù)遍歷,如果匹配到就走13,未匹配到則走_(dá)_objc_msgSend_uncached
__objc_msgSend_uncached
- __objc_msgSend_uncached調(diào)用 MethodTableLookup
- MethodTableLookup調(diào)用 _lookUpImpOrForward(c語(yǔ)言方法)
- 首先檢查類是否注冊(cè),初始化類、父類以及元類,做查找準(zhǔn)備
- 判斷是否需要再次查詢緩存,需要?jiǎng)t查一次,查到則 done_unlock,否則繼續(xù)往下
- 調(diào)用getmethodnosupre_nolock,由于運(yùn)行時(shí)的存在獲得的methods是一個(gè)二維數(shù)組,循環(huán)獲取調(diào)用search_method_list_inline查找方法
- search_method_list_inline中判斷方法列表是否排序,未排序則直接暴力循環(huán)查找,排序則調(diào)用多態(tài)方法 findMethodInSortedMethodList
- 在findMethodInSortedMethodList方法中調(diào)用它的多態(tài)方法多一個(gè)參數(shù)的findMethodInSortedMethodList,多出來(lái)的參數(shù)根據(jù)架構(gòu)不同傳參不同
- 多參數(shù)的findMethodInSortedMethodList方法中,使用位運(yùn)算的二分查找搜索對(duì)應(yīng)的方法,多出來(lái)的參數(shù)是通過(guò)method_t的首地址獲取methon的sel的函數(shù),在對(duì)比中 通過(guò)傳入的sel和獲取到的sel對(duì)比來(lái)判斷是否相等
- 如果沒(méi)找到對(duì)應(yīng)的方法,則判斷是否父類是否存在,不存在為局部變量imp賦值為_objc_msgForward_impcache消息轉(zhuǎn)發(fā)方法,然后break 走向11;如果找到方法則走done 13
- 如果父類存在則查詢父類的緩存,找到則break往下走到done 13,未找到則繼續(xù)循環(huán),查詢父類的方法列表,一直重復(fù),只到觸發(fā)9的父類不存在走向11
- 用接受者類調(diào)用 resolveMethod_locked,此方法中根據(jù)接收者類是實(shí)例類還是元類,分別調(diào)用 resolveInstanceMethod或者 resolveClassMethod,兩者都調(diào)用 lookUpImpOrNilTryCache查找方法,一個(gè)查找 resolveInstanceMethod:,一個(gè)查找 resolveClassMethod:,找到后就調(diào)用(這樣就走到了oc層面的動(dòng)態(tài)解析),在調(diào)用完上述方法后,則會(huì)再次調(diào)用 lookUpImpOrForwardTryCache,lookUpImpOrForwardTryCache會(huì)再次調(diào)用lookUpImpOrForward
- 在11調(diào)用完后,這個(gè)時(shí)候已經(jīng)標(biāo)記為動(dòng)態(tài)解析過(guò),則不會(huì)再次走11,則走到了調(diào)用9里面賦值的_objc_msgForward_impcache,里面會(huì)調(diào)用一個(gè)未開源的 forwarding方法,在里面則是會(huì)調(diào)用走到oc的消息轉(zhuǎn)發(fā)的第二步,第二步走不通,則調(diào)用第三步,第三步走不通則調(diào)用doesnotrecognizeselecoter報(bào)錯(cuò)
- 找到方法后,調(diào)用 log_and_fill_cache,在其中再調(diào)用cache的insert方法將sel和imp放入接收者類的緩存;結(jié)束后再返回imp
cache_insert:
解決幾個(gè)問(wèn)題:
- 搜尋內(nèi)存擴(kuò)充機(jī)制 —> 大于4分之3,則翻倍申請(qǐng)空間,并將之前緩存的空間釋放掉,會(huì)丟失之前存儲(chǔ)的方法
- 探索哈希函數(shù)的構(gòu)成 —> (sel ^ (sel >> 7)) & mask
- 探索哈希沖突的解決方案 —> i = i-1,if i == 0, i == mask(末尾),繼續(xù)i=i-1
- 找到為什么cache里面的imp需要異或isa才能得到真實(shí)imp的原因 —> bucket set方法,將imp異或isa后才存入的,所以取出的時(shí)候需要再次異或得到原值
x為之前已存入cache的方法數(shù)
- 計(jì)算如果存入此方法后的緩存大小 x+1 = X;拿到當(dāng)前實(shí)際申請(qǐng)的地址空間大小 = Y
- 根據(jù)緩存的占用量做分支判斷;
2.1 如果x==0并且沒(méi)有申請(qǐng)過(guò)緩存空間,則創(chuàng)建緩存,默認(rèn)大小為1<<2 【初始化】
2.2 如果X小于等于 4分之3Y,則什么也不做
2.3 如果X大于了4分之3Y,則重新申請(qǐng)內(nèi)存空間,申請(qǐng)的大小為Y的2倍,如果Y為0則是默認(rèn)大小1<<2
注:在重新申請(qǐng)空間時(shí),會(huì)將之前的cache里面存的方法全丟掉。實(shí)際上是指向了新申請(qǐng)的內(nèi)存空間塊,之前的直接釋放了。 - 存入cache中 —> 內(nèi)存擴(kuò)充機(jī)制
3.1 通過(guò)哈希函數(shù)cache_hash算出下標(biāo)位置i —> sel右移7位與sel異或,再與上mask mask等于 當(dāng)前申請(qǐng)的容量-1 實(shí)際就是以0開始的末尾下標(biāo)
3.2 如果下標(biāo)位置中不存在數(shù)據(jù),則沒(méi)有出現(xiàn)哈希沖突,則調(diào)用bucket的set方法存入,并返回 —> set方法里對(duì)imp進(jìn)行了編碼,編碼規(guī)則為 imp異或isa
3.3 如果下標(biāo)位置存在數(shù)據(jù),則調(diào)用cache_next解決沖突,解決方式為 i=i-1,如果i到頭部了,則將i指向表的末尾mask,繼續(xù)遍歷i-1位置到起始i結(jié)束
3.4 如果遍歷完成仍然沒(méi)有找到合適位置,則調(diào)用bad_cache報(bào)錯(cuò),找到的話則插入并返回
Cache_getimp
- GetClassFromIsa_p16 得到isa
- CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
- 執(zhí)行匯編搜索isa的cache,找到后執(zhí)行cachehint
- 判斷找到的imp是否為nil,如果是nil直接返回到上一級(jí)6,不是nil則走 AuthAndResignAsIMP
- 和 TailCallCachedImp一樣的isa異或imp得到真實(shí)的imp地址 —> 因?yàn)閷mp存入cache時(shí)編碼了一次 就是用的 imp異或isa,則cache里面存儲(chǔ)的值是異或過(guò)后的值。
- 調(diào)用 LGetImpMissDynamic 返回nil
OC 層面:
第一步: 動(dòng)態(tài)解析【動(dòng)態(tài)添加方法】
BOOL + resolveInstanceMethod:SEL
BOOL + resolveClassMethod:SEL
動(dòng)態(tài)給self的sel添加imp和methodtype
本質(zhì)是構(gòu)建一個(gè)新的方法 讓sel指向新的方法
可以是c語(yǔ)言方法 可以是oc方法
c語(yǔ)言方法直接書寫type,oc可以通過(guò)method類獲取
v16@0:8
v 表示返回值,16表示v在函數(shù)中的地址為函數(shù)首地址偏移16,@表示id,0表示其地址為函數(shù)首地址,:表示SEL,起地址偏移量為8
添加后返回yes,沒(méi)添加返回no 返回值看源碼實(shí)際沒(méi)有使用
如果實(shí)現(xiàn)了此方法,則在源碼中會(huì)多走一次重復(fù)流程,不論是否真的動(dòng)態(tài)添加了方法,在第二次中則會(huì)往下走第二步;
如果沒(méi)有實(shí)現(xiàn)則直接走向第二步消息轉(zhuǎn)發(fā)
第二步: 消息轉(zhuǎn)發(fā)
id - forwardingTargetForSelector:SEL
重新生成一個(gè)實(shí)例對(duì)象返回,讓它處理SEL
如果未實(shí)現(xiàn)或者返回nil,則走向第三步
第三步:
-
生成方法簽名
NSMethodSignatrue - methodSigntureForSelector:SEL如果生成的話則走向2.
否則就報(bào)錯(cuò),未找到方法 dosenotrecognizeSelector 方法簽名包裝后,隨意處理
此時(shí)已經(jīng)不崩潰了
void - forwardInvocation:NSInvocation
Invocation中包含了 之前原始的target、SEL以及上一個(gè)方法生成的簽名