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

消息發(fā)送(Messaging)

8臭蚁、以上便是runtime相關(guān)的一些數(shù)據(jù)結(jié)構(gòu)最铁,接下來我們回看一開始的疑問:
objc_msgSend()函數(shù)在執(zhí)行的過程中是如何找到對應的類,找到對應的方法實現(xiàn)的呢垮兑?
這就是消息發(fā)送(messaging)的處理過程了:
(1)冷尉、對于上文的Class的數(shù)據(jù)結(jié)構(gòu)的描述,官方文檔只簡略了把它們歸納成了兩部分:一個指向其父類的指針和一個方法調(diào)用表(這個Class的所有方法的selector和實現(xiàn)代碼所在地址的關(guān)聯(lián)表)系枪;
(2)雀哨、當某個消息被發(fā)送到一個對象之后(即對象執(zhí)行某個方法),runtime會根據(jù)這個對象的isa指針找到它所屬的類私爷,在類的方法調(diào)用表里查找對應的selector雾棺。如果沒有找到的話,它會繼續(xù)沿著類的super_class指針找到它的父類衬浑,在父類的方法調(diào)用表里查找對應的selector捌浩;
(3)、找到了對應的selector之后工秩,就根據(jù)selector找到方法的實現(xiàn)代碼的地址尸饺,執(zhí)行這些實現(xiàn)的代碼。如果沒有找到助币,則會啟用消息轉(zhuǎn)發(fā)(message forwarding)機制浪听,這個機制在后文會詳談;
(4)奠支、所以一個方法的實現(xiàn)代碼馋辈,并不是在編譯的時候就確定好的抚芦,它是直到調(diào)用這個方法的時候倍谜,才通過消息發(fā)送機制迈螟,定位到方法的實現(xiàn)代碼處執(zhí)行,所以方法的調(diào)用和實現(xiàn)是動態(tài)綁定(dynamically bound)的尔崔;
(5)答毫、當執(zhí)行方法的實現(xiàn)代碼的時候,objc_msgSend()函數(shù)不止會把實現(xiàn)代碼需要的參數(shù)傳給它季春,同時還會多傳兩個隱藏參數(shù):self和_cmd洗搂。這兩個參數(shù)其實就是objc_msgSend(receiver, selector)的receiver和selector,表面上objc_msgSend()函數(shù)只是把receiver和selector之后的那些參數(shù)傳給了方法的實現(xiàn)代碼(如果后面還有參數(shù)的話)载弄,實際上它偷偷地把receiver和selector也給傳進去了耘拇,方法的實現(xiàn)代碼里使用self和_cmd這兩個形參就能調(diào)用到receiver和selector。
所以為什么當我們在編寫一個方法的代碼的時候宇攻,使用“self”就能直接調(diào)用到這個方法調(diào)用的對象惫叛,就是通過這個過程傳遞進來的;
(6)逞刷、為了提高消息發(fā)送的速度嘉涌,每次在查找方法調(diào)用表前,會先查找一個類的cache(見前文7(7))夸浅,cache里存放了常用的方法的selector和實現(xiàn)代碼地址的對應關(guān)系仑最,如果在cache里能夠找到對應的selector,那就可以直接跳到方法的實現(xiàn)代碼處做執(zhí)行帆喇,不需要再去跑剩下的消息發(fā)送流程警医。
判斷方法是否“常用”依照了這樣一個原則:如果一個方法被調(diào)用了一次,那么它就很有可能會被調(diào)用第二次坯钦,這個方法就會被加入cache法严。如果程序運行了足夠久,讓cache做了足夠的熱身(warn up)葫笼,那么程序的運行會比一開始的時候更快深啤,此時幾乎所有需要調(diào)用的方法都能在cache里找到。
(7)路星、官方提供的消息發(fā)送的流程圖如下:


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

9溯街、那么還有一個疑問沒有討論,就是如果在消息發(fā)送的過程中發(fā)生了意外的話洋丐,它又會怎么處理呢呈昔?其實也就是8(3)中所提到的:如果消息發(fā)送沒能找到對應的方法,那么runtime就會啟用消息轉(zhuǎn)發(fā)(message forwarding)機制來進行處理友绝。
首先我們知道堤尾,正常情況下我們會在類的@implementation寫好方法的實現(xiàn)代碼,當執(zhí)行這個方法的時候迁客,runtime最終會綁定到這段實現(xiàn)代碼并執(zhí)行它郭宝,這是正常的流程辞槐。如果沒有找到對應的實現(xiàn)代碼,那么runtime會依次按照下面三個步驟來處理這個消息:
(1)粘室、其實runtime并不會立刻就啟動消息轉(zhuǎn)發(fā)榄檬,首先runtime會做的是動態(tài)方法解析(Dynamic Method Resolution)。它調(diào)用當前類的類方法+resolveInstanceMethod:(處理實例方法)或+resolveClassMethod:(處理類方法)衔统,看看是否在方法中有動態(tài)添消息的方法實現(xiàn)鹿榜,有則執(zhí)行,無則繼續(xù)下一步處理锦爵;
(2)舱殿、如果來到這一步,才是真正地開始消息轉(zhuǎn)發(fā)了险掀。runtime首先會進行快速轉(zhuǎn)發(fā)(Fast Forwarding)怀薛,它會調(diào)用當前類的- (id)forwardingTargetForSelector:方法,看看方法中是否有將此消息轉(zhuǎn)發(fā)給其他類的處理迷郑,有則將消息轉(zhuǎn)發(fā)給對應處理的類枝恋,無則繼續(xù)下一步處理;
(3)嗡害、最后runtime會進行完整的消息轉(zhuǎn)發(fā)(Normal Forwarding)焚碌,它首先會調(diào)用- (NSMethodSignature *)methodSignatureForSelector:方法,如果方法能正常返回一個NSMethodSignature對象霸妹,那么它就會創(chuàng)建一個表示消息的NSInvocation對象十电,這個對象包含了消息相關(guān)的所有細節(jié)阀圾,然后調(diào)用- (void)forwardInvocation:方法進行完整的轉(zhuǎn)發(fā)睁搭,如果- (void)forwardInvocation:方法中有對這個消息的相關(guān)轉(zhuǎn)發(fā)處理,就將消息轉(zhuǎn)發(fā)給對應的另一類進行處理處理存筏,如果沒有罢绽,則拋出unrecognized selector sent to instance或者unrecognized selector sent to class的異常信息畏线。
這就是一個完整的消息轉(zhuǎn)發(fā)處理流程。

10良价、我們可以通過@samlaudev的一個demo驗證整個轉(zhuǎn)發(fā)過程:
(1)寝殴、首先定義了一個Message類,并在類中定義了一個實例方法:



當調(diào)用了這個方法的時候:



會有如下輸出:

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

(2)蚣常、然后我們首先來驗證第一步:動態(tài)方法解析。
將-(void)sendMessage:方法的實現(xiàn)代碼注掉痊银,同時添加以下方法:



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

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

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

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

說明runtime執(zhí)行了消息轉(zhuǎn)發(fā)的第一步躺酒;
(4)、最后我們來驗證處理的第三步蔑歌,即是消息轉(zhuǎn)發(fā)的第二步:將消息完整轉(zhuǎn)發(fā)給其他類處理羹应。
此時我們再新建一個類MessageNormalForwading,并在MessageNormalForwading類中也定義一個-(void)message:方法:



回到Message類次屠,將第二步的-(id) forwardingTargetForSelector:方法注掉园匹,然后添加以下兩個方法:

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

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

動態(tài)解析類方法和類型編碼

11本昏、在10(2)所處理的第一步中供汛,如果需要動態(tài)解析的方法是類方法,應該怎么處理呢涌穆?
我們給Message類聲明一個類方法+(void)classSendMessage:并且不做任何實現(xiàn)怔昨,然后需要在Message類中添加這樣一個方法來處理:



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



輸出如下:

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

當我們添加實例方法的時候趁舀,class_addMethod()函數(shù)第一個參數(shù)傳的是[self class],傳當前的類祝沸;而添加類方法的時候赫编,就需要傳[self class]所屬的類,當前類所屬的類奋隶,即是元類(Meta Class)擂送。
這正是我們在前文討論過的,在類的method_list里添加方法唯欣,會成為它的實例可調(diào)用的方法嘹吨,即是這個類的實例方法;在類所屬的元類的method_list里添加方法境氢,會成為元類的實例可調(diào)用的方法蟀拷,元類的實例即是當前類碰纬,于是成為了這個類的類方法。

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

13墨微、還有一個地方可以注意一下:在動態(tài)方法解析和完整消息轉(zhuǎn)發(fā)中的相關(guān)方法中道媚,都出現(xiàn)了這么一個字符串:"v@*",這個字符串是類型編碼翘县,它將消息中的方法歸納成幾個字符串來表示最域。
比如上文消息轉(zhuǎn)發(fā)的例子中,消息里的方法是-(void)message:锈麸,于是"v@*"中的v表示方法返回值為void羡宙,*表示方法的參數(shù)是NSString類型的,@則表示隱藏參數(shù)self掐隐。
隱藏參數(shù)在類型編碼中是可寫可不寫的狗热,所以考慮到還有另外一個隱藏參數(shù)_cmd,這個類型編碼寫成"v@:*"也是可以的虑省。當然直接寫成"v*"也沒問題匿刮。

參考文檔:
官方文檔
https://github.com/samlaudev/RuntimeDemo
http://www.reibang.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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市探颈,隨后出現(xiàn)的幾起案子熟丸,更是在濱河造成了極大的恐慌,老刑警劉巖伪节,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件光羞,死亡現(xiàn)場離奇詭異,居然都是意外死亡怀大,警方通過查閱死者的電腦和手機纱兑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來化借,“玉大人潜慎,你說我怎么就攤上這事。” “怎么了铐炫?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵垒手,是天一觀的道長。 經(jīng)常有香客問我倒信,道長科贬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任鳖悠,我火速辦了婚禮榜掌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竞穷。我一直安慰自己唐责,他們只是感情好鳞溉,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布瘾带。 她就那樣靜靜地躺著,像睡著了一般熟菲。 火紅的嫁衣襯著肌膚如雪看政。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天抄罕,我揣著相機與錄音允蚣,去河邊找鬼。 笑死呆贿,一個胖子當著我的面吹牛嚷兔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播做入,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冒晰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了竟块?” 一聲冷哼從身側(cè)響起壶运,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浪秘,沒想到半個月后蒋情,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡耸携,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年棵癣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夺衍。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浙巫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情的畴,我是刑警寧澤渊抄,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丧裁,受9級特大地震影響护桦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煎娇,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一二庵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缓呛,春花似錦催享、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至票髓,卻和暖如春攀涵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洽沟。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工以故, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裆操。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓怒详,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踪区。 傳聞我的和親對象是個殘疾皇子昆烁,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

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

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