消息轉(zhuǎn)發(fā)全流程

源碼層面:

調(diào)用_objc_msgSend 匯編

  1. 先查找cache_t,沒(méi)有的話找methodlist
  2. 指向父類,并重復(fù)1
  3. 如果最后父類都沒(méi)有實(shí)現(xiàn)則走到OC層面的轉(zhuǎn)發(fā)流程
  4. 如果找到了則將方法放入當(dāng)前接受者類的cache中加入此方法,如果加入的時(shí)候判斷,cache列表的空間即將用完,則清空cache并加倍申請(qǐng)空間,并將方法加入cache中,并調(diào)用此方法

源碼解讀:

  1. 對(duì)receiver判空
  2. 空則調(diào)用LNilOrTagged或者LReturnZero,否則3 —> 這就是我們給nil發(fā)送方法不會(huì)出問(wèn)題的原因,因?yàn)橹苯泳头祷亓?/li>
  3. 將receiver的地址賦值給p13,調(diào)用 GetClassFromIsa_p16
  4. GetClassFromIsa_p16調(diào)用ExtractISA計(jì)算地址
  5. 經(jīng)過(guò)運(yùn)算【receiver的地址與上isamask】得到isa地址
  6. 接著往下走 LGetisaDone(標(biāo)簽),調(diào)用 CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
  7. 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)志位
  8. cache_t的地址與上 #0x0000fffffffffffe得到buckets的地址
  9. 判斷cache_t地址的第0位是否為0,不為0則跳轉(zhuǎn)到LLookupPreopt,否則10
  10. 將sel右移7位與sel異或,再將cachet_t右移48位得到mask,將兩者相與得到要查找的方法在buckets中的下標(biāo) —> 這里本質(zhì)是利用哈希函數(shù)得到下表 cache_hash
  11. 從當(dāng)前方法應(yīng)該在buckets中的地址往前開始遍歷buckets,判斷bucket的sel是否和要查找的相同,找到則走cacheHit 13,未找到則往下14 —> 向前遍歷的原因是因?yàn)楣_突的解決方案是下標(biāo)位置-1,如果前面都放滿了則指向buckets的尾部,再往前找位置 cache_next
  12. 每次判斷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)找到的流程。
  13. cachehint會(huì)調(diào)用TailCallCachedImp,將cache中找到的imp異或上isa得到真實(shí)的imp地址返回。 —> 因?yàn)閷mp存入cache時(shí)編碼了一次 就是用的 imp異或isa,則cache里面存儲(chǔ)的值是異或過(guò)后的值。
  14. 計(jì)算出當(dāng)前buckets的尾部地址,從此地址往前遍歷到上一次循環(huán)前得到的首地址,避免重復(fù)遍歷,如果匹配到就走13,未匹配到則走_(dá)_objc_msgSend_uncached
__objc_msgSend_uncached
  1. __objc_msgSend_uncached調(diào)用 MethodTableLookup
  2. MethodTableLookup調(diào)用 _lookUpImpOrForward(c語(yǔ)言方法)
  3. 首先檢查類是否注冊(cè),初始化類、父類以及元類,做查找準(zhǔn)備
  4. 判斷是否需要再次查詢緩存,需要?jiǎng)t查一次,查到則 done_unlock,否則繼續(xù)往下
  5. 調(diào)用getmethodnosupre_nolock,由于運(yùn)行時(shí)的存在獲得的methods是一個(gè)二維數(shù)組,循環(huán)獲取調(diào)用search_method_list_inline查找方法
  6. search_method_list_inline中判斷方法列表是否排序,未排序則直接暴力循環(huán)查找,排序則調(diào)用多態(tài)方法 findMethodInSortedMethodList
  7. 在findMethodInSortedMethodList方法中調(diào)用它的多態(tài)方法多一個(gè)參數(shù)的findMethodInSortedMethodList,多出來(lái)的參數(shù)根據(jù)架構(gòu)不同傳參不同
  8. 多參數(shù)的findMethodInSortedMethodList方法中,使用位運(yùn)算的二分查找搜索對(duì)應(yīng)的方法,多出來(lái)的參數(shù)是通過(guò)method_t的首地址獲取methon的sel的函數(shù),在對(duì)比中 通過(guò)傳入的sel和獲取到的sel對(duì)比來(lái)判斷是否相等
  9. 如果沒(méi)找到對(duì)應(yīng)的方法,則判斷是否父類是否存在,不存在為局部變量imp賦值為_objc_msgForward_impcache消息轉(zhuǎn)發(fā)方法,然后break 走向11;如果找到方法則走done 13
  10. 如果父類存在則查詢父類的緩存,找到則break往下走到done 13,未找到則繼續(xù)循環(huán),查詢父類的方法列表,一直重復(fù),只到觸發(fā)9的父類不存在走向11
  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
  12. 在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ò)
  13. 找到方法后,調(diào)用 log_and_fill_cache,在其中再調(diào)用cache的insert方法將sel和imp放入接收者類的緩存;結(jié)束后再返回imp

cache_insert:

解決幾個(gè)問(wèn)題:

  1. 搜尋內(nèi)存擴(kuò)充機(jī)制 —> 大于4分之3,則翻倍申請(qǐng)空間,并將之前緩存的空間釋放掉,會(huì)丟失之前存儲(chǔ)的方法
  2. 探索哈希函數(shù)的構(gòu)成 —> (sel ^ (sel >> 7)) & mask
  3. 探索哈希沖突的解決方案 —> i = i-1,if i == 0, i == mask(末尾),繼續(xù)i=i-1
  4. 找到為什么cache里面的imp需要異或isa才能得到真實(shí)imp的原因 —> bucket set方法,將imp異或isa后才存入的,所以取出的時(shí)候需要再次異或得到原值

x為之前已存入cache的方法數(shù)

  1. 計(jì)算如果存入此方法后的緩存大小 x+1 = X;拿到當(dāng)前實(shí)際申請(qǐng)的地址空間大小 = Y
  2. 根據(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)存空間塊,之前的直接釋放了。
  3. 存入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

  1. GetClassFromIsa_p16 得到isa
  2. CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
  3. 執(zhí)行匯編搜索isa的cache,找到后執(zhí)行cachehint
  4. 判斷找到的imp是否為nil,如果是nil直接返回到上一級(jí)6,不是nil則走 AuthAndResignAsIMP
  5. 和 TailCallCachedImp一樣的isa異或imp得到真實(shí)的imp地址 —> 因?yàn)閷mp存入cache時(shí)編碼了一次 就是用的 imp異或isa,則cache里面存儲(chǔ)的值是異或過(guò)后的值。
  6. 調(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,則走向第三步

第三步:

  1. 生成方法簽名
    NSMethodSignatrue - methodSigntureForSelector:SEL

    如果生成的話則走向2.
    否則就報(bào)錯(cuò),未找到方法 dosenotrecognizeSelector

  2. 方法簽名包裝后,隨意處理

此時(shí)已經(jīng)不崩潰了

void - forwardInvocation:NSInvocation

Invocation中包含了 之前原始的target、SEL以及上一個(gè)方法生成的簽名

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容