Runtime-消息三步處理機(jī)制

Runtime 方法調(diào)用本質(zhì)

OC是一門(mén)runtime語(yǔ)言照弥,OC調(diào)用方法的實(shí)際僻爽,其實(shí)就是消息轉(zhuǎn)發(fā)顿锰,我們可以通過(guò)底層C++源碼來(lái)探索方法調(diào)用的本質(zhì):xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

舉個(gè)例子:

OC:[person test];

C++:((void(*)(id, SEL))(void*)objc_msgSend)((id)person, sel_registerName("test"));

通過(guò)這段源碼可以看出c++底層源碼中方法調(diào)用最終都是通過(guò)objc_msgSend函數(shù)蛔糯,這種模式在OC領(lǐng)域中叫做消息機(jī)制陈瘦,表示給方法的調(diào)用者發(fā)送消息幌甘,上面中person代表消息接受者;test代表消息名稱

消息幾個(gè)過(guò)程

消息發(fā)送階段:負(fù)責(zé)從類或者父類的緩存列表或者baseMethodList方法列表中查找方法痊项;

動(dòng)態(tài)解析階段:如果消息發(fā)送階段沒(méi)有找到方法锅风,則會(huì)進(jìn)入動(dòng)態(tài)解析階段,負(fù)責(zé)動(dòng)態(tài)的添加方法實(shí)現(xiàn)

消息轉(zhuǎn)發(fā)階段:如果消息發(fā)送階段沒(méi)有找到方法鞍泉,動(dòng)態(tài)解析又沒(méi)有添加對(duì)應(yīng)的方法皱埠,則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段,將消息轉(zhuǎn)發(fā)給可以處理消息的接受者來(lái)處理咖驮。

如果前面這幾個(gè)過(guò)程都沒(méi)有實(shí)現(xiàn)到的話边器,那么就會(huì)爆出一個(gè)經(jīng)典的錯(cuò)誤方法:unrecognzied selector sent to instance,接下來(lái)我們來(lái)細(xì)看這幾個(gè)步驟:

消息發(fā)送階段

在runtime源碼中搜索_objc_msgSend查看其源碼內(nèi)部實(shí)現(xiàn)托修,在objc-msg-arm64.s匯編文件中可以查看到_objc_msgSend函數(shù)的實(shí)現(xiàn)流程圖和源碼圖:

_objc_msgSend流程圖
_objc_msgSend函數(shù)

上述匯編源碼中會(huì)首先判斷消息接受者x0忘巧,是否為nil,如果為nil則會(huì)執(zhí)行LNilOrTagged睦刃,LNiOrTagged內(nèi)部會(huì)執(zhí)行LReturnZero(內(nèi)部直接return 0);

如果傳入的消息接受者不為nil砚嘴,這直接往下執(zhí)行,CacheLookUp眯勾,CacheLookUp內(nèi)部會(huì)對(duì)方法列表進(jìn)行查找枣宫,如果找到則執(zhí)行CacheHit,進(jìn)而調(diào)用方法吃环,否則執(zhí)行CheckMiss也颤,CheckMiss內(nèi)部調(diào)用__objc_msgSend_uncached

CacheLoopup

可以看到CacheLookup內(nèi)部方法緩存列表進(jìn)行查找,如果找到則執(zhí)行CacheHit郁轻,進(jìn)而調(diào)用方法翅娶,否則就會(huì)執(zhí)行CheckMiss,CheckMiss內(nèi)部調(diào)用__objc_msgSend_uncached.

__objc_msgSend_uncached

__objc_msgSend_uncached 內(nèi)部會(huì)執(zhí)行MethodTableLookup也就是方法列表查找

__class_lookupMethodAndLoadCache3

__class_lookupMethodAndLoadCache3 是匯編語(yǔ)言好唯,而對(duì)于方法_class_lookupMethodAndLoadCache3在匯編中會(huì)生成__class_lookupMethodAndLoadCache3竭沫,所以這個(gè)時(shí)候我們發(fā)現(xiàn)我們能在C++源碼中找到這個(gè)方法的實(shí)現(xiàn)_class_lookupMethodAndLoadCache3

部分源碼圖

具體全部源碼可以直接找源碼來(lái)看,這里不一一描述骑篙,下面我將通過(guò)一張圖來(lái)說(shuō)明一下這個(gè)流程先:

_class_lookMethodAndLoadCache3內(nèi)部流程

總結(jié)圖:

總結(jié)圖

動(dòng)態(tài)解析階段

接上面流程圖的紅色部分繼續(xù)尋找下去蜕提,該部分的源碼是:

_class_resolveMethod動(dòng)態(tài)解析入口
_class_resolveMethod動(dòng)態(tài)內(nèi)部實(shí)現(xiàn)

從內(nèi)部代碼中實(shí)現(xiàn)可以看到動(dòng)態(tài)解析方法之后,會(huì)將triedResolver = YES;那么下次就不會(huì)在進(jìn)行動(dòng)態(tài)解析階段了靶端,雖然會(huì)重新執(zhí)行retry谎势,會(huì)重新對(duì)方法查找一遍凛膏,但是之后無(wú)論我們有沒(méi)有動(dòng)態(tài)解析成功,都不會(huì)再次進(jìn)行動(dòng)態(tài)解析脏榆。

動(dòng)態(tài)解析方法的實(shí)現(xiàn)和驗(yàn)證

動(dòng)態(tài)解析對(duì)象方法時(shí)猖毫,會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel方法。

動(dòng)態(tài)解析類方法時(shí)须喂,會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel方法吁断。

Person 類動(dòng)態(tài)解析
動(dòng)態(tài)解析測(cè)試

上面的代碼中可以看出,person在調(diào)用方法test時(shí)候坞生,test其實(shí)在person.m中并沒(méi)有實(shí)現(xiàn)仔役,但是可以看到[person test]之后并沒(méi)有爆出錯(cuò)誤,反而能執(zhí)行成功恨胚,通過(guò)上面對(duì)消息發(fā)送我們知道骂因,當(dāng)本類和父類cache和class_rw_t中都找不到方法時(shí),就會(huì)進(jìn)行動(dòng)態(tài)解析的方法赃泡,也就是說(shuō)會(huì)自動(dòng)調(diào)用類的resolveInstanceMethod:方法進(jìn)行動(dòng)態(tài)查找,因此當(dāng)我們手動(dòng)執(zhí)行resolveInstanceMethod的時(shí)候乘盼,可以按照我們的邏輯自己來(lái)實(shí)現(xiàn)升熊,從上面中可以看到我們r(jià)esolveInstanceMethod方法可以看到內(nèi)部使用到了class_addMethod,并且用other方法類替代test方法的實(shí)現(xiàn),接下來(lái)看看class_addMethod函數(shù)具體實(shí)現(xiàn)

class_addMethod 函數(shù)

class_addMethod(__unsafe_unretainedClass cls, SEL name, IMP imp,constchar*types)

第一個(gè)參數(shù):cls給那個(gè)類添加方法

第二個(gè)參數(shù):SEL name添加方法的名稱

第三個(gè)參數(shù):IMP imp方法的實(shí)現(xiàn)

第四個(gè)參數(shù):types方法類型

另外還有一個(gè)class_getInstanceMethod方法獲取Method绸栅,這個(gè)Method其實(shí)是一個(gè)objc_method類型結(jié)構(gòu)體级野,其實(shí)也可以理解為內(nèi)部結(jié)構(gòu)同method_t結(jié)構(gòu)體相同,method_t是代表方法的結(jié)構(gòu)體粹胯,其內(nèi)部包含SEL蓖柔、type、IMP风纠,我們通過(guò)自定義method_t結(jié)構(gòu)體况鸣,將objc_method強(qiáng)轉(zhuǎn)為method_t查看方法是否能夠動(dòng)態(tài)添加成功


objc_method強(qiáng)轉(zhuǎn)為method_t

從終端打印信息可以看出,我們的猜測(cè)是對(duì)的

不依賴method_getImplementation函數(shù)和method_getTypeEncoding函數(shù)獲取方法的imp和type竹观,手動(dòng)書(shū)寫(xiě)镐捧,如下圖所示:

手動(dòng)書(shū)寫(xiě)

動(dòng)態(tài)解析類方法

類方法動(dòng)態(tài)解析

無(wú)論我們是否實(shí)現(xiàn)了動(dòng)態(tài)解析的方法,系統(tǒng)內(nèi)部都會(huì)執(zhí)行retry對(duì)方法再次進(jìn)行查找臭增,那么如果我們實(shí)現(xiàn)了動(dòng)態(tài)解析方法懂酱,此時(shí)就會(huì)順利查找到方法,進(jìn)而返回imp對(duì)方法進(jìn)行調(diào)用誊抛。如果我們沒(méi)有實(shí)現(xiàn)動(dòng)態(tài)解析方法列牺。就會(huì)進(jìn)行消息轉(zhuǎn)發(fā)。

動(dòng)態(tài)解析流程圖:

動(dòng)態(tài)解析方法流程圖

消息轉(zhuǎn)發(fā)階段

從源碼中可以看出拗窃,如果我們經(jīng)過(guò)消息發(fā)送階段找不到瞎领,然后到消息動(dòng)態(tài)解析泌辫,都還沒(méi)有找到消息實(shí)現(xiàn)者的話,就會(huì)信息消息轉(zhuǎn)發(fā)階段默刚,會(huì)調(diào)用_objc_msgForward_imcache函數(shù)甥郑,發(fā)現(xiàn)在C++源碼中找不到_objc_msgForward_imcache函數(shù),但是我們可以在匯編中找到__objc_msgForward_impcache函數(shù)中找到__objc_msgForward進(jìn)而找到__objc_forward_handler

__objc_msgFoward_impcache
__objc_forward_handler

從這里可以看到這個(gè)方法僅僅只是一個(gè)錯(cuò)誤的信息輸出荤西,因?yàn)橄⑥D(zhuǎn)發(fā)機(jī)制是不開(kāi)源的澜搅,但是還是有一個(gè)消息轉(zhuǎn)發(fā)的過(guò)程,那應(yīng)該這個(gè)消息轉(zhuǎn)發(fā)有可能是返回了某種類型的對(duì)象邪锌,該對(duì)象實(shí)現(xiàn)相應(yīng)的方法勉躺。通過(guò)代碼來(lái)實(shí)現(xiàn)一下:

forwardingTargetForSelector

從上述例子中可以看出,當(dāng)本類沒(méi)有實(shí)現(xiàn)方法觅丰,并且沒(méi)有動(dòng)態(tài)解析方法饵溅,就會(huì)調(diào)用forwardingTargetForSelector函數(shù),進(jìn)行消息轉(zhuǎn)發(fā)妇萄,因此我們可以實(shí)現(xiàn)forwardingTargetForSelector函數(shù)蜕企,在其內(nèi)部將消息轉(zhuǎn)發(fā)給可以實(shí)現(xiàn)此方法的對(duì)象,如果forwardingTargetForSelector函數(shù)返回nil冠句,或者沒(méi)有實(shí)現(xiàn)的話轻掩,就會(huì)調(diào)用methodSignatureForSelector方法,用來(lái)返回一個(gè)方法簽名懦底,這個(gè)是消息機(jī)制中最后的機(jī)會(huì)了唇牧,如果methodSignatureForSelector方法返回正確的方法簽名就會(huì)調(diào)用forwardInvocation方法,forwardInvocation方法內(nèi)提供一個(gè)NSInvocation類型的參數(shù)聚唐,NSInvocation封裝了一個(gè)方法的調(diào)用丐重,包括方法的調(diào)用者,方法名杆查,以及方法的參數(shù)扮惦,因此我們可以在forwardInvocation函數(shù)內(nèi)修改方法調(diào)用對(duì)象就可以了,最后如果methodSignatureForSelector返回的為nil根灯,就會(huì)來(lái)到doseNotRecognizeSelector:方法內(nèi)部径缅,程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance;

驗(yàn)證一下:將上面的代碼更改一下烙肺,

消息轉(zhuǎn)發(fā)最后機(jī)制

流程圖纳猪,接上面的消息轉(zhuǎn)發(fā)機(jī)制:

消息轉(zhuǎn)發(fā)階段

詳解NSInvocation

methodSignatureForSelector 方法中返回的方法簽名,在forwardInvocation中被包裝成NSInvocation對(duì)象桃笙,NSInvocation提供了獲取和修改方法名氏堤,參數(shù),返回值等方法,簡(jiǎn)單來(lái)說(shuō)鼠锈,在forwardInvoation函數(shù)中我們可以做絕對(duì)的修改闪檬;

舉例說(shuō)明一下,現(xiàn)在我們?yōu)樯厦娴膁riving方法添加返回值和參數(shù)购笆,并在forwardInvocation方法中修改方法的返回值及參數(shù)粗悯。

nsinvocation

從上述打印結(jié)果可以看出forwardInvocation方法中可以對(duì)方法的參數(shù)及返回值進(jìn)行修改。

類方法的消息轉(zhuǎn)發(fā)

類方法消息轉(zhuǎn)發(fā)同對(duì)象方法一樣同欠,同樣需要經(jīng)過(guò)消息發(fā)送样傍,動(dòng)態(tài)方法解析之后才會(huì)進(jìn)行消息轉(zhuǎn)發(fā)機(jī)制。我們知道類方法是存儲(chǔ)在元類對(duì)象中的铺遂,元類對(duì)象本來(lái)也是一種特殊的類對(duì)象衫哥。需要注意的是,類方法的消息接受者變?yōu)轭悓?duì)象襟锐。

當(dāng)類對(duì)象進(jìn)行消息轉(zhuǎn)發(fā)時(shí)撤逢,對(duì)調(diào)用相應(yīng)的+號(hào)的forwardingTargetForSelector、methodSignatureForSelector粮坞、forwardInvocation方法蚊荣,需要注意的是+號(hào)方法僅僅沒(méi)有提示,而不是系統(tǒng)不會(huì)對(duì)類方法進(jìn)行消息轉(zhuǎn)發(fā)莫杈。

總結(jié)消息處理機(jī)制

OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用妇押,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)。方法調(diào)用過(guò)程中也就是objc_msgSend底層實(shí)現(xiàn)分為三個(gè)階段:消息發(fā)送姓迅、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)俊马,而這三個(gè)具體的實(shí)現(xiàn)和流程上面都說(shuō)得很清楚了哈丁存,有漏或者錯(cuò)誤的地方,閱讀者可以提出留言哈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柴我,一起剝皮案震驚了整個(gè)濱河市解寝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艘儒,老刑警劉巖聋伦,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異界睁,居然都是意外死亡觉增,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)翻斟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逾礁,“玉大人,你說(shuō)我怎么就攤上這事访惜∴诼模” “怎么了腻扇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)砾嫉。 經(jīng)常有香客問(wèn)我幼苛,道長(zhǎng),這世上最難降的妖魔是什么焕刮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任舶沿,我火速辦了婚禮,結(jié)果婚禮上济锄,老公的妹妹穿的比我還像新娘暑椰。我一直安慰自己,他們只是感情好荐绝,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布一汽。 她就那樣靜靜地躺著,像睡著了一般低滩。 火紅的嫁衣襯著肌膚如雪召夹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天恕沫,我揣著相機(jī)與錄音监憎,去河邊找鬼。 笑死婶溯,一個(gè)胖子當(dāng)著我的面吹牛鲸阔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迄委,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼褐筛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叙身?” 一聲冷哼從身側(cè)響起渔扎,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎信轿,沒(méi)想到半個(gè)月后晃痴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡财忽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年倘核,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片定罢。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笤虫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琼蚯,我是刑警寧澤酬凳,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站遭庶,受9級(jí)特大地震影響宁仔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峦睡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一翎苫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧榨了,春花似錦煎谍、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至转捕,卻和暖如春作岖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背五芝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工痘儡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢步。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓沉删,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親醉途。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丑念,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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