前言
????????我們知道OC是一門動態(tài)語言料睛、它提供了一個RunTime庫把代碼中的類型檢測耍属、方法調(diào)用等一系列操作放到了運行期萄唇。這固然對語言的靈活性來說是一極大的優(yōu)勢寸宵,但這也給我們開發(fā)帶來了一個讓人頭疼的問題 ?--> ?《unrecognized selector sent to instance 0x1c400f420》崖面;相信我們在開發(fā)中都遇到過這種的Crash提示。
????????下面列舉幾個導致這種Crash Log的示例代碼梯影。
示例1巫员、
示例2、
????????正如我們開篇所敘述的那樣在編譯和鏈接期甲棍、上面兩個代碼沒有任何問題简识、但是在運行期就會出現(xiàn)?*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ACTestForwardObject length]: unrecognized selector sent to instance 0x1c0202fc0'這樣的Crash Log。
????????那么我們?nèi)绾伪苊馍厦娴倪@些問題呢救军?在Crash之前OC到底做了什么财异?我們是否可以在Crash之前對這個錯誤進行補救呢倘零?
????????答案是肯定的唱遭、OC在?unrecognized?selector 之前其實已經(jīng)為我們做了很多次努力(具體來說是三次)來讓我們有足夠的機會來挽回APP出現(xiàn)Crash,而這就是我們要說的消息轉(zhuǎn)發(fā)機制呈驶。
一拷泽、什么是消息轉(zhuǎn)發(fā)(Message Forward)
????????我們知道OC對象調(diào)用方法其實是調(diào)用底層的objc_msgSend()函數(shù),而方法查找的大致過程是通過遍歷當前類->父類->根類的方法列表來查找是否有對應的方法(這里對方法調(diào)用不做過多敘述)袖瞻。如果在查找過程中找到對應的方法實現(xiàn)司致,則進行方法調(diào)用;如果直到根類依然沒有找到對應的方法實現(xiàn)聋迎、那么接下來便是消息轉(zhuǎn)發(fā) --> Show Time!!!!
????????如果直到根類都沒有遇到傳說中的方法實現(xiàn)脂矫,那么OC將觸發(fā)消息轉(zhuǎn)發(fā)機制。而所謂的消息轉(zhuǎn)發(fā)大概過程如下圖:
? ? ? ? 從圖中我們可以看到如果在繼承關系的方法列表中沒有找到Method霉晕,將進行下面的三步去處理不能識別的方法庭再。
<1>、通過resolveInstanceMethod:我們可以動態(tài)的為類添加方法實現(xiàn)牺堰。
<2>拄轻、通過forwardingTargetForSelector:我們可以返回一個可以處理aSelector的對象。
<3>伟葫、通過methodSignatureForSelector:(方法簽名)和forwardInvocation:(封裝方法到Invocation)做最后的掙扎恨搓。
如果上面三步對aSelector做了處理,則程序正常執(zhí)行。否者程序最后將調(diào)用doesNotRecognizeSelector:方法輸出前言中的Crash Log斧抱。
二常拓、消息轉(zhuǎn)發(fā)代碼實現(xiàn)
????????首先我們實現(xiàn)兩個類:ACTestForwardObject和ACTestInvicationObject。
1.ACTestInvicationObject實現(xiàn)????
????????ACTestInvicationObject主要作為上面消息轉(zhuǎn)發(fā)中的Target返回辉浦,所以該類只實現(xiàn)一個方法如下圖:
2.?ACTestForwardObject實現(xiàn)
ACTestForwardObject.h中我們聲明一個logClassMethod函數(shù)用于輸出對象的方法列表內(nèi)容墩邀、如下圖。
依次在ACTestForwardObject.m中重寫或?qū)崿F(xiàn)消息轉(zhuǎn)發(fā)相關的函數(shù)盏浙、如下圖:
消息轉(zhuǎn)發(fā)的第一步:
消息轉(zhuǎn)發(fā)的第二步:
消息轉(zhuǎn)發(fā)的第三步:
該函數(shù)主要返回方法簽名眉睹,如果返回Nil,forwardInvocation將不會調(diào)用废膘、直接調(diào)用doesNotRecognizeSelector 導致Crash竹海。
這里對anInvocation對象做處理,最后invoke Invocation丐黄。
重寫doesNotRecognizeSelector進行Log輸出斋配。
其他私有函數(shù)實現(xiàn):
第三步Invocation被替換的方法實現(xiàn):
第一步動態(tài)添加的方法實現(xiàn):
用于輸出方法列表的函數(shù):
三、程序調(diào)試驗證
我們在程序啟動的時候調(diào)用如下代碼:
驗證一:
按照我們截圖的代碼運行程序輸出Log如下:(注意這里第二步和第三步中我取了非灌闺,實際沒有進入判斷條件)根據(jù)Log我們再回看圖P1-1-1消息轉(zhuǎn)發(fā)過程是不是更清楚了?.
驗證二:
? ? ? ? 在驗證一條件的基礎上艰争,將P-2-3中的isResolve條件取反,并放開注釋掉的????IMPdefaultMethod =(IMP)acDefaultRecognizedSelector; class_addMethod(self.class, sel, defaultMethod,"v@:");重新運行程序輸出Log如下:
驗證三:
? ? ? ? 在驗證一條件的基礎上桂对,將圖P-2-4中的[targetObjectrespondsToSelector:@selector(length)]前面的取非去掉甩卓,重新運行程序,輸出Log如下:
驗證四:
? ? 在驗證一條件的基礎上蕉斜,將圖P-2-6中的isEqual前面的取反去掉逾柿,重新運行程序,輸出Log如下:
驗證五:
在驗證一條件的基礎上宅此,我們將圖P-2-5中的[method isEqualToString:@"length"]取反机错;并將圖P-2-6中的isEqual前面的取反去掉,重新運行程序父腕。輸出Log如下:
四弱匪、總結(jié)
? ??????????通過上面的1、2璧亮、3萧诫、4驗證及Log輸出,我們可以相信OC中的消息轉(zhuǎn)發(fā)分為三個步驟:(1)杜顺、resolveInstanceMethod财搁,(2)、forwardingTargetForSelector躬络,(3)methodSignatureForSelector +?forwardInvocation尖奔;這三個步驟是按照順序依次調(diào)用的、如果步驟1對aSelector進行了處理、那么之后的2提茁、3將不在執(zhí)行淹禾;依次類推如果三步都沒有對aSelector做處理那么將調(diào)用doesNotRecognizeSelector之后輸出系統(tǒng)Log程序Crash。通過驗證5我們可以推斷出第三步中methodSignatureForSelector是必要條件茴扁。如果methodSignatureForSelector返回Nil铃岔,則forwardInvocation將不會被調(diào)用。