在上一篇文章中,我們了解了方法的慢速查找流程以及動態(tài)方法決議,在動態(tài)方法決議之后其實還不會崩潰,后面還會走到消息轉(zhuǎn)發(fā)流程, 這一篇文章就來了解一下消息的轉(zhuǎn)發(fā)機制。
在講解消息轉(zhuǎn)發(fā)機制之前,我們先來補充一下類方法的動態(tài)決議。

由圖可以看出,對象方法直接調(diào)用了resolveInstanceMethod, 然而類方法調(diào)用完resolveClassMethod還有一個判斷條件,如果需要查找cls類中有該方法的實現(xiàn)還會走一次resolveInstanceMethod,為什么會這樣呢?
這里是因為類方法在元類中是對象方法的形式存在,所以還是需要查詢元類中的對象方法。但由于我們無法在元類里面進行操作,所以只能根據(jù)元類的走向,最終會來到NSObject,可以在NSObject里面進行resolveInstanceMethod。
這里其實又是isa的走位圖,元類的最終指向是NSObject ,類的最終指向也是NSObject,所以我們可以建一個NSObject分類,將對象方法和類方法的動態(tài)決議都放在NSObject里面,如下圖:

這樣的話就非常的便利了,然后再封裝一個SDK,所有的方法報錯都可以在這里面得到處理。
但是這樣的話有可能其它類的動態(tài)方法決議寫在了他自己的分類里面,會搞的很亂。 所以接下來進入我們的主角,用消息轉(zhuǎn)發(fā)來處理崩潰。
消息轉(zhuǎn)發(fā)
在動態(tài)方法決議完了之后還沒找到imp,會來到消息轉(zhuǎn)發(fā),但是我們找了半天,也沒找不到消息轉(zhuǎn)發(fā)相關(guān)的源碼。走在金字塔頂端的人永遠是少數(shù), 這里的話一般人又玩不了了,那么怎么辦呢, 有兩種方法:
1、通過instrumentObjcMessageSends方式打印發(fā)送消息的日志
2、通過Hopper或者IDA反編譯的方式(Hopper要錢,IDA不要錢,但要在windows下運行才穩(wěn)定)
首先我們來看instrumentObjcMessageSends


從源碼中我們可以看出里面有一個控制開關(guān),根據(jù)objcMsgLogEnabled是否打開來判斷是否調(diào)用logMessageSend打印日志,接下來我們就調(diào)用instrumentObjcMessageSends將objcMsgLogEnabled設(shè)置成YES,設(shè)置成YES之后就會來到logMessageSend打印日志
由于instrumentObjcMessageSends是內(nèi)部的方法,在外面無法直接調(diào)用,所以我們用extern讓它被外界知道,然后將sayHello包起來

包起來之后運行代碼,運行完了之后,我們根據(jù)logMessageSend里面的/tmp/msgSends路徑來到msgSends,發(fā)現(xiàn)msgSends下有一個 msgSends-2376,這個里面就是打印的日志信息,我們打開來看一下

看到這里,整個崩潰前的調(diào)用流程我想大家都清楚了,我怕有的人還不清楚,我再截個圖注釋一下。這個地方說明一下,在慢速轉(zhuǎn)發(fā)簽名之后還會調(diào)用一次resolveInstanceMethod,系統(tǒng)給了一次不讓漂流瓶流出去的機會。所以加上簽名之后的一次,resolveInstanceMethod總共會來兩次。如果在簽名之前把消息處理了就只會來一次

我們先來看看消息快速轉(zhuǎn)發(fā),官方的解釋是如果這個消息沒有人接收的時候,返回它的第一繼承人去實現(xiàn),比如我在forwardingTargetForSelector返回[LGStudent alloc],LGStudent里面定義了sayHello,運行,結(jié)果就不報錯了,這就是牛逼的消息快速轉(zhuǎn)發(fā)流程

再來看看我們的消息慢速轉(zhuǎn)發(fā)流程,需要注意的是,慢速轉(zhuǎn)發(fā)需要返回一個消息簽名,不能返回空,簽名如果返回空就會報錯。然后根據(jù)官方文檔的解釋再調(diào)用forwardInvocation將消息拋出去給其他對象

看完官方文檔之后我們用代碼來進行慢速查找一下

沒有崩潰,非常的完美, 那么anInvocation究竟是什么玩意呢,我們點開來看一下

發(fā)現(xiàn)anInvocation里面的有方法的信息,那么為什么沒有崩潰了呢,其實就是把anInvocation這個事務(wù)拋出去了,就跟漂流瓶一樣。 同時我們還可以將這個事務(wù)保存下來,以便知道方法崩潰的信息,所以慢速轉(zhuǎn)發(fā)更加靈活,更加舒服。
所以整個的消息流程就如下面這幅圖:

接下來我們再通過反編譯的方式來查看流程
這里需要用一個工具Hopper,這個工具可以將我們可執(zhí)行文件反匯編成偽代碼、控制流程圖等
我們把程序運行崩潰,然后查看堆棧信息,發(fā)現(xiàn)___forwarding___來自CoreFoundation

但是我們怎么找都找不到CoreFoundation的源碼, 蘋果閉源了, 所以這時候我們就要用到Hopper通過反匯編來查看。
我們通過image list讀取鏡像找到CoreFoundation文件路徑,然后找到可執(zhí)行文件

找到可執(zhí)行文件后,打開hopper,選擇Try the Demo,然后將可執(zhí)行文件拖入hopper進行反匯編,選擇x86(64 bits),拖進去之后神奇的一幕就發(fā)生了,我們將CoreFoundation可執(zhí)行文件進行反匯編了,反匯編之后我們?nèi)炙阉?code>__forwarding_prep_0___

找到之后我們點進去,點進去之后發(fā)現(xiàn)看不懂,看不懂就再點擊上面的偽代碼按鈕,如下圖

點完偽代碼之后,我們再點擊____forwarding___,往下看, 會來到forwardingTargetForSelector: 快速轉(zhuǎn)發(fā)流程

如果快速轉(zhuǎn)發(fā)沒有響應(yīng),或者返回值為空的話,則會跳轉(zhuǎn)至loc_64a67進入慢速轉(zhuǎn)發(fā)流程


在慢速轉(zhuǎn)發(fā)過程中,如果沒有響應(yīng)methodSignatureForSelector,則走到loc_64dd7直接報錯。如果獲取methodSignatureForSelector的方法簽名為nil,則走到loc_64e3c里面報錯。
如果返回的方法簽名不為nil,則會走到forwardInvocation方法中,對invocation事務(wù)進行處理,如果不處理也不會報錯

好了,通過反匯編的形式又將我上面發(fā)的那一幅流程圖走了一遍,這就是動態(tài)方法決議之后進行的消息快速轉(zhuǎn)發(fā)和消息的慢速轉(zhuǎn)發(fā)。