上一篇文章初步講了一下objc_msgSend
的做用,這裏是傳送門初步理解objc_msgSend 。那麼,對象在收到消息以後沒法經過objc_msgSend
發送的消息以後會怎麼辦呢?數據庫
因爲OC是動態語言,因此在運行時還能夠繼續向類中添加方法,因此當對象收到沒法解讀的消息時,就會啓動消息轉發
機制,咱們能夠經由這個機制告訴程序該怎麼處理這種消息。設計模式
消息轉發會分紅兩大階段,第一階段叫作動態方法解析(dynamic method resolution):先徵詢當前接收者所屬的類,是否能動態添加方法並處理這個未知的selector,若是接收者沒有動態添加方法或者動態添加的方法依然不能處理這個未知的selector,則當前接收者本身就沒有辦法經過動態新增方法的手段來響應這個selector了,以後就進入消息轉發的第二階段。第二階段能夠分紅兩步,第一步接收者會查看是否存在其餘對象能處理這條消息,若是有,則這個處理消息的對象叫備援接收者(replacement receiver),runtime系統會把消息轉發給這個對象,消息轉發流程結束。第二步,若是連備援接收者都沒有,則啓動完整的消息轉發,runtime系統會把和消息有關的全部信息都放進NSInvocation對象中,再給接收者一次機會,處理未知的selector,若是這一步都失敗了,就會拋出unrecognize selector send to instance xxx這個異常。post
看完上面這段文字,再結合下面這張消息轉發流程圖,應該就能對整個消息轉發流程有個比較形象的認知了: spa
下面具體看看每一步的過程。設計
對象在收到沒法解讀的消息後,會先調用所屬類的一個類方法:code
+ (BOOL)resolveInstanceMethod:(SEL)selector;
複製代碼
該方法的參數就是objc_msgSend
沒法處理的selector,返回的布爾值表示這個類可否新增一個實例方法處理它。若是這個seletor不是一個實例方法而是一個類方法,那麼會有個相似的類方法調用:cdn
+ (BOOL)resolveClassMethod:(SEL)selector;
複製代碼
在這個階段處理未知selector的前提是相關代碼已經提早寫好了,只等着運行時動態插入類中就能夠了。對象
這個方案常常用來實現@dynamic屬性
,coreData中的NSManagerObject中的屬性就是這麼作的。因爲CoreData的屬性須要從數據庫中讀取,而後進行動態綁定,而不是經過自動生成的setter和getter去實現。其屬性getter和setter實現方法在編譯期就已經寫好,等到動態方法解析的時候進行setter和getter的方法添加。blog
在這一步中,runtime系統會提供一個方法,讓當前接收者返回一個備援接收者來處理未知的selector,這個方法以下:繼承
- (id)forwardingTargetForSelector:(SEL)selector;
複製代碼
若是當前接收者能找到或者提供這樣一個對象,就將其返回,不能就返回nil。
咱們能夠利用這一部,來模擬多重繼承
的一些特性。好比在一個對象內部,還有其餘不少對象,這個對象能夠經由這個方法,選擇一個處理selector的對象並返回。在外部看來,好像對象是親自處理該對象的同樣。
也能夠利用這個過程去完成一些很棒的設計模式,好比裝飾器
模式。具體的例子,會在後續文章更新中慢慢補上。
這裏要注意的是,在這個過程當中,是沒法操做經由這一步轉發的消息的。
當前兩步都宣告失敗以後,runtime系統會建立一個NSInvocation對象,把還沒有處理的消息與有關的細節信息所有封裝到裏面,這個對象包含selector,target以及參數。這個步驟會調用如下方法轉發消息:
- (void)forwardInvation:(NSInvocation*)invocation;
複製代碼
實現這個方法很簡單,只要改變目標,讓消息在新目標中調用便可。也能夠經過改變參數、更換selector等,變得應用場景更加多變。
這個方法實現時,若是發現不該該由原本調用forwardInvation
方法,就須要調用它父類的同名方法,這樣,繼承體系中全部的類都有機會處理調用請求,一直到NSObject。若是最後調用了NSObject類的方法,最後會以doesNotRocgnizeSelector
的方式拋出異常。
上面說過,在第二步備援接收者中是沒法處理消息的。而在完整的消息轉發
中不只可以操做消息,還能輕鬆拿到消息相關的全部信息。因此,一些看似黑魔法的實現實際上就在完整的消息轉發
這個過程當中實現的,好比JSPatch
和Aspects
這兩個開源庫關鍵的步驟就是在這個過程當中完成的。