一号胚、正常的消息傳遞
在C等語言中驱显,調(diào)用一個(gè)方法就是執(zhí)行內(nèi)存中的一段代碼刃宵,這在編譯的時(shí)候就決定好了级乍,所以沒有動(dòng)態(tài)的特性,而在Objective-C中竣贪,方法的調(diào)用會(huì)被編譯成消息的發(fā)送军洼,調(diào)用某個(gè)對(duì)象的方法,其實(shí)是在運(yùn)行時(shí)給對(duì)象發(fā)送了一條消息演怎, 例如匕争,下面的代碼是等價(jià)的:
[array insertObject:obj atIndex:0];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 0);
那么,runtime又是如何處理objc_msgSend發(fā)送的消息呢爷耀?從發(fā)送消息到最終的方法調(diào)用甘桑,這之間經(jīng)歷了一個(gè)怎樣的過程?
objc_msgSend的源碼是使用匯編寫的歹叮,本人匯編渣跑杭,無法從源碼角度去分析,不過從詳盡的注釋和相關(guān)的文檔咆耿,我們可以一窺其面目德谅,objc_msgSend會(huì)經(jīng)過以下步驟:
1、通過對(duì)象的isa指針找到對(duì)象所屬的class
2萨螺、在class的方法緩存(objc_cache)中查找方法窄做,如果沒有找到,則繼續(xù)3慰技、4步驟
3椭盏、在class的method_list中查找方法
4、如果class中沒有找到方法惹盼,則繼續(xù)往它的super class中查找, 直到查找到根類
5庸汗、一旦找到方法,就去執(zhí)行對(duì)應(yīng)的方法實(shí)現(xiàn)(IMP)手报,并把方法添加到方法緩存中
在這個(gè)方法查找過程中蚯舱,runtime引入了緩存機(jī)制改化,這是為了提高方法查找的效率,因?yàn)橥骰瑁绻{(diào)用的方法在根類中陈肛,那么每次方法調(diào)用都要沿著繼承鏈去每個(gè)類的方法列表中查找一遍,效率無疑是低下的兄裂。這個(gè)方法緩存的實(shí)現(xiàn)句旱,其實(shí)就是一個(gè)哈希表的存儲(chǔ),以selector name的哈希值為索引, 存儲(chǔ)方法的實(shí)現(xiàn)(IMP)晰奖,這樣的查找效率較高谈撒,看到這里,可能有人會(huì)有疑問匾南,既然每個(gè)class維護(hù)著一個(gè)方法緩存的哈希表啃匿,為什么還要維護(hù)一個(gè)方法列表method list呢?每次直接去哈希表里查找方法不是更快嗎蛆楞?
這其實(shí)是因?yàn)楣1硎且粋€(gè)無序表溯乒,而方法列表是一個(gè)有序列表,查找方法會(huì)順著method list依次查找豹爹,這樣就賦予了category一個(gè)特性:可以覆蓋原本類的實(shí)現(xiàn)裆悄,而如果是使用哈希表,則無法保證順序臂聋。關(guān)于category的原理和具體實(shí)現(xiàn)光稼,將在后續(xù)的文章中探討。
二孩等、異常的消息傳遞
2.1钟哥、普通的函數(shù)調(diào)用
Person * person = [[Person alloc]init];
[person A];
如果 函數(shù)A 未聲明方法
結(jié)果:編譯失敗
2.2、利用消息傳遞來調(diào)用函數(shù)
objc_msgSend(person, @selector(resolveThisMethodDynamically));
結(jié)果:編譯成功
原因:你可以隨時(shí)對(duì)一個(gè)對(duì)象傳遞任何消息瞎访,而不需要在編譯的時(shí)候聲明這些方法。所以O(shè)bjective-C可以在runtime的時(shí)候傳遞消息吁恍。
在上面的查找方法的過程中扒秸,如果最終沒有查找到目標(biāo)方法,會(huì)導(dǎo)致crash冀瓦,但是在crash之前伴奥,runtime會(huì)給我們2次機(jī)會(huì)去挽救程序:
Dynamic Method Resolution
Message Forwarding
總結(jié)
Objective-C中給一個(gè)對(duì)象發(fā)送消息會(huì)經(jīng)過以下幾個(gè)步驟:
在對(duì)象的類中查找selector,如果找到了翼闽,執(zhí)行對(duì)應(yīng)函數(shù)的IMP,查找過程會(huì)使用緩存拾徙,并且沿著類的繼承關(guān)系鏈查找
如果沒有找到,Runtime 會(huì)發(fā)送+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個(gè)消息
如果 resolve 方法返回 NO感局,Runtime 就發(fā)送-forwardingTargetForSelector:允許你把這個(gè)消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象
如果沒有新的目標(biāo)對(duì)象返回尼啡, Runtime 就會(huì)發(fā)送-methodSignatureForSelector:和-forwardInvocation:消息暂衡。你可以發(fā)送-invokeWithTarget:消息來手動(dòng)轉(zhuǎn)發(fā)消息或者發(fā)送-doesNotRecognizeSelector:拋出異常。
demo地址:https://github.com/lionsom/LXRunTimeAll 項(xiàng)目007
此demo只簡單的驗(yàn)證的了“Dynamic Method Resolution”
“Message Forwarding”未成功Q虏t】癯玻?/strong>
參考文檔:http://zziking.github.io/ios/2016/02/09/Objective-C_Runtime_2_Messaging_and_forwarding.html