前言
????????我們知道OC是一門動(dòng)態(tài)語(yǔ)言、它提供了一個(gè)RunTime庫(kù)把代碼中的類型檢測(cè)、方法調(diào)用等一系列操作放到了運(yùn)行期。這固然對(duì)語(yǔ)言的靈活性來說是一極大的優(yōu)勢(shì),但這也給我們開發(fā)帶來了一個(gè)讓人頭疼的問題 ?--> ?《unrecognized selector sent to instance 0x1c400f420》;相信我們?cè)陂_發(fā)中都遇到過這種的Crash提示。
????????下面列舉幾個(gè)導(dǎo)致這種Crash Log的示例代碼。
示例1、

示例2、



????????正如我們開篇所敘述的那樣在編譯和鏈接期、上面兩個(gè)代碼沒有任何問題、但是在運(yùn)行期就會(huì)出現(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之前對(duì)這個(gè)錯(cuò)誤進(jìn)行補(bǔ)救呢?
????????答案是肯定的、OC在?unrecognized?selector 之前其實(shí)已經(jīng)為我們做了很多次努力(具體來說是三次)來讓我們有足夠的機(jī)會(huì)來挽回APP出現(xiàn)Crash,而這就是我們要說的消息轉(zhuǎn)發(fā)機(jī)制。
一、什么是消息轉(zhuǎn)發(fā)(Message Forward)
????????我們知道OC對(duì)象調(diào)用方法其實(shí)是調(diào)用底層的objc_msgSend()函數(shù),而方法查找的大致過程是通過遍歷當(dāng)前類->父類->根類的方法列表來查找是否有對(duì)應(yīng)的方法(這里對(duì)方法調(diào)用不做過多敘述)。如果在查找過程中找到對(duì)應(yīng)的方法實(shí)現(xiàn),則進(jìn)行方法調(diào)用;如果直到根類依然沒有找到對(duì)應(yīng)的方法實(shí)現(xiàn)、那么接下來便是消息轉(zhuǎn)發(fā) --> Show Time!!!!
????????如果直到根類都沒有遇到傳說中的方法實(shí)現(xiàn),那么OC將觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制。而所謂的消息轉(zhuǎn)發(fā)大概過程如下圖:

? ? ? ? 從圖中我們可以看到如果在繼承關(guān)系的方法列表中沒有找到Method,將進(jìn)行下面的三步去處理不能識(shí)別的方法。
<1>、通過resolveInstanceMethod:我們可以動(dòng)態(tài)的為類添加方法實(shí)現(xiàn)。
<2>、通過forwardingTargetForSelector:我們可以返回一個(gè)可以處理aSelector的對(duì)象。
<3>、通過methodSignatureForSelector:(方法簽名)和forwardInvocation:(封裝方法到Invocation)做最后的掙扎。
如果上面三步對(duì)aSelector做了處理,則程序正常執(zhí)行。否者程序最后將調(diào)用doesNotRecognizeSelector:方法輸出前言中的Crash Log。
二、消息轉(zhuǎn)發(fā)代碼實(shí)現(xiàn)
????????首先我們實(shí)現(xiàn)兩個(gè)類:ACTestForwardObject和ACTestInvicationObject。
1.ACTestInvicationObject實(shí)現(xiàn)????
????????ACTestInvicationObject主要作為上面消息轉(zhuǎn)發(fā)中的Target返回,所以該類只實(shí)現(xiàn)一個(gè)方法如下圖:

2.?ACTestForwardObject實(shí)現(xiàn)
ACTestForwardObject.h中我們聲明一個(gè)logClassMethod函數(shù)用于輸出對(duì)象的方法列表內(nèi)容、如下圖。

依次在ACTestForwardObject.m中重寫或?qū)崿F(xiàn)消息轉(zhuǎn)發(fā)相關(guān)的函數(shù)、如下圖:
消息轉(zhuǎn)發(fā)的第一步:

消息轉(zhuǎn)發(fā)的第二步:

消息轉(zhuǎn)發(fā)的第三步:
該函數(shù)主要返回方法簽名,如果返回Nil,forwardInvocation將不會(huì)調(diào)用、直接調(diào)用doesNotRecognizeSelector 導(dǎo)致Crash。

這里對(duì)anInvocation對(duì)象做處理,最后invoke Invocation。

重寫doesNotRecognizeSelector進(jìn)行Log輸出。

其他私有函數(shù)實(shí)現(xiàn):
第三步Invocation被替換的方法實(shí)現(xiàn):

第一步動(dòng)態(tài)添加的方法實(shí)現(xiàn):

用于輸出方法列表的函數(shù):

三、程序調(diào)試驗(yàn)證
我們?cè)诔绦騿?dòng)的時(shí)候調(diào)用如下代碼:

驗(yàn)證一:
按照我們截圖的代碼運(yùn)行程序輸出Log如下:(注意這里第二步和第三步中我取了非,實(shí)際沒有進(jìn)入判斷條件)根據(jù)Log我們?cè)倩乜磮DP1-1-1消息轉(zhuǎn)發(fā)過程是不是更清楚了?.

驗(yàn)證二:
? ? ? ? 在驗(yàn)證一條件的基礎(chǔ)上,將P-2-3中的isResolve條件取反,并放開注釋掉的????IMPdefaultMethod =(IMP)acDefaultRecognizedSelector; class_addMethod(self.class, sel, defaultMethod,"v@:");重新運(yùn)行程序輸出Log如下:

驗(yàn)證三:
? ? ? ? 在驗(yàn)證一條件的基礎(chǔ)上,將圖P-2-4中的[targetObjectrespondsToSelector:@selector(length)]前面的取非去掉,重新運(yùn)行程序,輸出Log如下:

驗(yàn)證四:
? ? 在驗(yàn)證一條件的基礎(chǔ)上,將圖P-2-6中的isEqual前面的取反去掉,重新運(yùn)行程序,輸出Log如下:

驗(yàn)證五:
在驗(yàn)證一條件的基礎(chǔ)上,我們將圖P-2-5中的[method isEqualToString:@"length"]取反;并將圖P-2-6中的isEqual前面的取反去掉,重新運(yùn)行程序。輸出Log如下:

四、總結(jié)
? ??????????通過上面的1、2、3、4驗(yàn)證及Log輸出,我們可以相信OC中的消息轉(zhuǎn)發(fā)分為三個(gè)步驟:(1)、resolveInstanceMethod,(2)、forwardingTargetForSelector,(3)methodSignatureForSelector +?forwardInvocation;這三個(gè)步驟是按照順序依次調(diào)用的、如果步驟1對(duì)aSelector進(jìn)行了處理、那么之后的2、3將不在執(zhí)行;依次類推如果三步都沒有對(duì)aSelector做處理那么將調(diào)用doesNotRecognizeSelector之后輸出系統(tǒng)Log程序Crash。通過驗(yàn)證5我們可以推斷出第三步中methodSignatureForSelector是必要條件。如果methodSignatureForSelector返回Nil,則forwardInvocation將不會(huì)被調(diào)用。