我所理解的Runtime:2、消息發(fā)送和消息轉(zhuǎn)發(fā)

消息發(fā)送(Messaging)

8、以上便是runtime相關(guān)的一些數(shù)據(jù)結(jié)構(gòu),接下來(lái)我們回看一開(kāi)始的疑問(wèn):
objc_msgSend()函數(shù)在執(zhí)行的過(guò)程中是如何找到對(duì)應(yīng)的類(lèi),找到對(duì)應(yīng)的方法實(shí)現(xiàn)的呢?
這就是消息發(fā)送(messaging)的處理過(guò)程了:
(1)、對(duì)于上文的Class的數(shù)據(jù)結(jié)構(gòu)的描述,官方文檔只簡(jiǎn)略了把它們歸納成了兩部分:一個(gè)指向其父類(lèi)的指針和一個(gè)方法調(diào)用表(這個(gè)Class的所有方法的selector和實(shí)現(xiàn)代碼所在地址的關(guān)聯(lián)表);
(2)、當(dāng)某個(gè)消息被發(fā)送到一個(gè)對(duì)象之后(即對(duì)象執(zhí)行某個(gè)方法),runtime會(huì)根據(jù)這個(gè)對(duì)象的isa指針找到它所屬的類(lèi),在類(lèi)的方法調(diào)用表里查找對(duì)應(yīng)的selector。如果沒(méi)有找到的話,它會(huì)繼續(xù)沿著類(lèi)的super_class指針找到它的父類(lèi),在父類(lèi)的方法調(diào)用表里查找對(duì)應(yīng)的selector;
(3)、找到了對(duì)應(yīng)的selector之后,就根據(jù)selector找到方法的實(shí)現(xiàn)代碼的地址,執(zhí)行這些實(shí)現(xiàn)的代碼。如果沒(méi)有找到,則會(huì)啟用消息轉(zhuǎn)發(fā)(message forwarding)機(jī)制,這個(gè)機(jī)制在后文會(huì)詳談;
(4)、所以一個(gè)方法的實(shí)現(xiàn)代碼,并不是在編譯的時(shí)候就確定好的,它是直到調(diào)用這個(gè)方法的時(shí)候,才通過(guò)消息發(fā)送機(jī)制,定位到方法的實(shí)現(xiàn)代碼處執(zhí)行,所以方法的調(diào)用和實(shí)現(xiàn)是動(dòng)態(tài)綁定(dynamically bound)的;
(5)、當(dāng)執(zhí)行方法的實(shí)現(xiàn)代碼的時(shí)候,objc_msgSend()函數(shù)不止會(huì)把實(shí)現(xiàn)代碼需要的參數(shù)傳給它,同時(shí)還會(huì)多傳兩個(gè)隱藏參數(shù):self和_cmd。這兩個(gè)參數(shù)其實(shí)就是objc_msgSend(receiver, selector)的receiver和selector,表面上objc_msgSend()函數(shù)只是把receiver和selector之后的那些參數(shù)傳給了方法的實(shí)現(xiàn)代碼(如果后面還有參數(shù)的話),實(shí)際上它偷偷地把receiver和selector也給傳進(jìn)去了,方法的實(shí)現(xiàn)代碼里使用self和_cmd這兩個(gè)形參就能調(diào)用到receiver和selector。
所以為什么當(dāng)我們?cè)诰帉?xiě)一個(gè)方法的代碼的時(shí)候,使用“self”就能直接調(diào)用到這個(gè)方法調(diào)用的對(duì)象,就是通過(guò)這個(gè)過(guò)程傳遞進(jìn)來(lái)的;
(6)、為了提高消息發(fā)送的速度,每次在查找方法調(diào)用表前,會(huì)先查找一個(gè)類(lèi)的cache(見(jiàn)前文7(7)),cache里存放了常用的方法的selector和實(shí)現(xiàn)代碼地址的對(duì)應(yīng)關(guān)系,如果在cache里能夠找到對(duì)應(yīng)的selector,那就可以直接跳到方法的實(shí)現(xiàn)代碼處做執(zhí)行,不需要再去跑剩下的消息發(fā)送流程。
判斷方法是否“常用”依照了這樣一個(gè)原則:如果一個(gè)方法被調(diào)用了一次,那么它就很有可能會(huì)被調(diào)用第二次,這個(gè)方法就會(huì)被加入cache。如果程序運(yùn)行了足夠久,讓cache做了足夠的熱身(warn up),那么程序的運(yùn)行會(huì)比一開(kāi)始的時(shí)候更快,此時(shí)幾乎所有需要調(diào)用的方法都能在cache里找到。
(7)、官方提供的消息發(fā)送的流程圖如下:


動(dòng)態(tài)方法解析(Dynamic Method Resolution)和消息轉(zhuǎn)發(fā)(Message Forwarding)

9、那么還有一個(gè)疑問(wèn)沒(méi)有討論,就是如果在消息發(fā)送的過(guò)程中發(fā)生了意外的話,它又會(huì)怎么處理呢?其實(shí)也就是8(3)中所提到的:如果消息發(fā)送沒(méi)能找到對(duì)應(yīng)的方法,那么runtime就會(huì)啟用消息轉(zhuǎn)發(fā)(message forwarding)機(jī)制來(lái)進(jìn)行處理。
首先我們知道,正常情況下我們會(huì)在類(lèi)的@implementation寫(xiě)好方法的實(shí)現(xiàn)代碼,當(dāng)執(zhí)行這個(gè)方法的時(shí)候,runtime最終會(huì)綁定到這段實(shí)現(xiàn)代碼并執(zhí)行它,這是正常的流程。如果沒(méi)有找到對(duì)應(yīng)的實(shí)現(xiàn)代碼,那么runtime會(huì)依次按照下面三個(gè)步驟來(lái)處理這個(gè)消息:
(1)、其實(shí)runtime并不會(huì)立刻就啟動(dòng)消息轉(zhuǎn)發(fā),首先runtime會(huì)做的是動(dòng)態(tài)方法解析(Dynamic Method Resolution)。它調(diào)用當(dāng)前類(lèi)的類(lèi)方法+resolveInstanceMethod:(處理實(shí)例方法)或+resolveClassMethod:(處理類(lèi)方法),看看是否在方法中有動(dòng)態(tài)添消息的方法實(shí)現(xiàn),有則執(zhí)行,無(wú)則繼續(xù)下一步處理;
(2)、如果來(lái)到這一步,才是真正地開(kāi)始消息轉(zhuǎn)發(fā)了。runtime首先會(huì)進(jìn)行快速轉(zhuǎn)發(fā)(Fast Forwarding),它會(huì)調(diào)用當(dāng)前類(lèi)的- (id)forwardingTargetForSelector:方法,看看方法中是否有將此消息轉(zhuǎn)發(fā)給其他類(lèi)的處理,有則將消息轉(zhuǎn)發(fā)給對(duì)應(yīng)處理的類(lèi),無(wú)則繼續(xù)下一步處理;
(3)、最后runtime會(huì)進(jìn)行完整的消息轉(zhuǎn)發(fā)(Normal Forwarding),它首先會(huì)調(diào)用- (NSMethodSignature *)methodSignatureForSelector:方法,如果方法能正常返回一個(gè)NSMethodSignature對(duì)象,那么它就會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,這個(gè)對(duì)象包含了消息相關(guān)的所有細(xì)節(jié),然后調(diào)用- (void)forwardInvocation:方法進(jìn)行完整的轉(zhuǎn)發(fā),如果- (void)forwardInvocation:方法中有對(duì)這個(gè)消息的相關(guān)轉(zhuǎn)發(fā)處理,就將消息轉(zhuǎn)發(fā)給對(duì)應(yīng)的另一類(lèi)進(jìn)行處理處理,如果沒(méi)有,則拋出unrecognized selector sent to instance或者unrecognized selector sent to class的異常信息。
這就是一個(gè)完整的消息轉(zhuǎn)發(fā)處理流程。

10、我們可以通過(guò)@samlaudev的一個(gè)demo驗(yàn)證整個(gè)轉(zhuǎn)發(fā)過(guò)程:
(1)、首先定義了一個(gè)Message類(lèi),并在類(lèi)中定義了一個(gè)實(shí)例方法:



當(dāng)調(diào)用了這個(gè)方法的時(shí)候:



會(huì)有如下輸出:

這是一個(gè)正常的方法執(zhí)行;

(2)、然后我們首先來(lái)驗(yàn)證第一步:動(dòng)態(tài)方法解析。
將-(void)sendMessage:方法的實(shí)現(xiàn)代碼注掉,同時(shí)添加以下方法:



這對(duì)應(yīng)處理的第一步,此時(shí)-(void)sendMessage:方法已經(jīng)沒(méi)有正常的實(shí)現(xiàn)代碼了,根據(jù)第一步,runtime會(huì)在+resolveInstanceMethod:方法中看看是否有動(dòng)態(tài)添加-(void)sendMessage:方法實(shí)現(xiàn),此時(shí)運(yùn)行后輸出:

說(shuō)明runtime確實(shí)執(zhí)行了動(dòng)態(tài)方法解析;
(3)、然后我們來(lái)驗(yàn)證第二步,即是消息轉(zhuǎn)發(fā)的第一步:快速轉(zhuǎn)發(fā)給其他類(lèi)處理。
此時(shí)需要新建一個(gè)其他類(lèi)MessageForwarding,然后在MessageForwarding類(lèi)中也定義一個(gè)-(void)message:方法:

然后回到Message類(lèi),把上一步的+resolveInstanceMethod:方法注掉,添加以下快速轉(zhuǎn)發(fā)的方法:

意思即是將這個(gè)消息快速轉(zhuǎn)發(fā)給MessageForwarding對(duì)象去處理,運(yùn)行輸出如下:

說(shuō)明runtime執(zhí)行了消息轉(zhuǎn)發(fā)的第一步;
(4)、最后我們來(lái)驗(yàn)證處理的第三步,即是消息轉(zhuǎn)發(fā)的第二步:將消息完整轉(zhuǎn)發(fā)給其他類(lèi)處理。
此時(shí)我們?cè)傩陆ㄒ粋€(gè)類(lèi)MessageNormalForwading,并在MessageNormalForwading類(lèi)中也定義一個(gè)-(void)message:方法:



回到Message類(lèi),將第二步的-(id) forwardingTargetForSelector:方法注掉,然后添加以下兩個(gè)方法:

將消息封裝成一個(gè)NSIncocation對(duì)象,然后將它完整轉(zhuǎn)發(fā)給MessageNormalForwading類(lèi)去處理。執(zhí)行后輸出:

說(shuō)明runtime完整地執(zhí)行了消息轉(zhuǎn)發(fā)的第二步。
由此我們驗(yàn)證了這三個(gè)步驟。

動(dòng)態(tài)解析類(lèi)方法和類(lèi)型編碼

11、在10(2)所處理的第一步中,如果需要?jiǎng)討B(tài)解析的方法是類(lèi)方法,應(yīng)該怎么處理呢?
我們給Message類(lèi)聲明一個(gè)類(lèi)方法+(void)classSendMessage:并且不做任何實(shí)現(xiàn),然后需要在Message類(lèi)中添加這樣一個(gè)方法來(lái)處理:



執(zhí)行以下代碼后:



輸出如下:

需要注意的地方是,在classSendMessage:方法內(nèi)執(zhí)行class_addMethod()函數(shù)時(shí)的第一個(gè)參數(shù)。

當(dāng)我們添加實(shí)例方法的時(shí)候,class_addMethod()函數(shù)第一個(gè)參數(shù)傳的是[self class],傳當(dāng)前的類(lèi);而添加類(lèi)方法的時(shí)候,就需要傳[self class]所屬的類(lèi),當(dāng)前類(lèi)所屬的類(lèi),即是元類(lèi)(Meta Class)。
這正是我們?cè)谇拔挠懻撨^(guò)的,在類(lèi)的method_list里添加方法,會(huì)成為它的實(shí)例可調(diào)用的方法,即是這個(gè)類(lèi)的實(shí)例方法;在類(lèi)所屬的元類(lèi)的method_list里添加方法,會(huì)成為元類(lèi)的實(shí)例可調(diào)用的方法,元類(lèi)的實(shí)例即是當(dāng)前類(lèi),于是成為了這個(gè)類(lèi)的類(lèi)方法。

12、消息轉(zhuǎn)發(fā)能讓一個(gè)類(lèi)通過(guò)把消息傳遞給其他類(lèi)處理,來(lái)處理一些它本來(lái)不能處理的方法,看起來(lái)似乎能模擬“多重繼承”的效果,通過(guò)把不同消息轉(zhuǎn)發(fā)給其他類(lèi)處理模擬了繼承自其他類(lèi)的效果。不過(guò)消息轉(zhuǎn)發(fā)雖然類(lèi)似于繼承,但NSObject的一些方法還是能區(qū)分兩者,如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈。

13、還有一個(gè)地方可以注意一下:在動(dòng)態(tài)方法解析和完整消息轉(zhuǎn)發(fā)中的相關(guān)方法中,都出現(xiàn)了這么一個(gè)字符串:"v@*",這個(gè)字符串是類(lèi)型編碼,它將消息中的方法歸納成幾個(gè)字符串來(lái)表示。
比如上文消息轉(zhuǎn)發(fā)的例子中,消息里的方法是-(void)message:,于是"v@*"中的v表示方法返回值為void,*表示方法的參數(shù)是NSString類(lèi)型的,@則表示隱藏參數(shù)self。
隱藏參數(shù)在類(lèi)型編碼中是可寫(xiě)可不寫(xiě)的,所以考慮到還有另外一個(gè)隱藏參數(shù)_cmd,這個(gè)類(lèi)型編碼寫(xiě)成"v@:*"也是可以的。當(dāng)然直接寫(xiě)成"v*"也沒(méi)問(wèn)題。

參考文檔:
官方文檔
https://github.com/samlaudev/RuntimeDemo
http://www.lxweimin.com/p/25a319aee33d
http://www.cocoachina.com/ios/20141105/10134.html
http://www.cocoachina.com/ios/20141106/10150.html

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,751評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,578評(píng)論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂(lè)樂(lè)的簡(jiǎn)書(shū)閱讀 2,147評(píng)論 0 9
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,223評(píng)論 0 7
  • 不喜歡就滾… 她轉(zhuǎn)過(guò)身離開(kāi),試圖遮掩她眼眶中的淚水。 看著她的離去背影我沒(méi)有追上去,我知道,我那句話語(yǔ)已經(jīng)傷透了她...
    ANYANA閱讀 296評(píng)論 0 0