消息發(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