一、簡介:
消息轉發是OC底層一種功能強大的實現,為OC方法的調用增加更多的表現力和容錯能力。什么是消息轉發?簡單來說,就是在OC在調用方法但不能找到方法對應實現時,執行的一種補救措施,從而將方法的執行引導到其他地方,為程序執行提供更多可能.
二、先來了解什么是消息發送
消息發送的官方定義:
OC的方法本質:
OC方法的底層實現就是objc_msgSend()
objc_msgSend()前面兩個參數:self
和SEL
。對self
的理解一般認為它是對象本身,官方文檔的解釋是指向接收此消息的對象的指針,其實也不難理解,按照runtime的邏輯,方法的執行先要查找到本類的方法列表,然后執行,因此就需要知道本類是誰。對于_cmd(它保存了正在發送的消息的選擇器)是第二個隱式參數,對應方法的實現。總之,self指向對象本身,_cmd指向方法本身。
OC中方法分為類方法和對象方法,對應的調用方式就是:
1.類名調用類方法:
// 對象實例調用
Person *person = [[Person alloc] init];
[person run];
2.對象實例調用實例方法:
// 類方法調用
[Person walk];
OC 函數調用的語法都會被翻譯成一個 C 的函數調用 objc_msgSend()
我們用對象調用方法來舉例子說明
1.先自定義一個Person類:
2.分別用person對象和消息發送來調用run方法:
3.查看打印結果:
4.可以看到打印了三次,說明方法被調用了三次.其中對象調用一次, objc_msgSend()調用了兩次. objc_msgSend()就是方法調用的底層實現.
說明:
可以看到上面寫了兩種方式的objc_msgSend()調用,這是是LLVM的配置選項,可以選擇關閉objc_msgSend的編寫檢查.具體操作如圖:
5.消息發送的具體細節實現會另外寫一篇文章.
二、消息轉發...
當沒有方法的實現,程序會在運行時掛掉并拋出 unrecognized selector sent to …
的異常。但在異常拋出前,Objective-C 的運行時會給你三次拯救程序的機會:
- 動態方法解析: Method Resolution
- 快速轉發: Fast Rorwarding
- 完整消息轉發: Normal Forwarding
系統在處理消息轉發的時候,是按照上面的順序進行轉發的,轉發成功則會跳過后面的方法.
1.動態方法解析: Method Resolution
首先,當調用沒有實現的方法的時候,Objective-C 運行時會調用 + (BOOL)resolveInstanceMethod:或者 + (BOOL)resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數并返回 YES, 那運行時系統就會重新啟動一次消息發送的過程。
這里
v
代表函數返回類型void,
@
代表self的類型id,
:
代表_cmd的類型SEL。
2.快速轉發: Fast Rorwarding
- 與
Method Resolution
不同,Fast Rorwarding
這是一種快速消息轉發:只需要在- (id)forwardingTargetForSelector:(SEL)aSelector
方法里面返回一個新對象即可。相當于替換了消息的接收者,進而去新的接收者那里去尋找對應的實現。
- 通過- (id)forwardingTargetForSelector:(SEL)aSelector方法。如果此方法返回的是新的消息接收對象,則會向新對象轉發此消息,如果此方法返回的是 nil 或者self,則會進入系統消息轉發機制。具體為向
- (void)forwardInvocation:(NSInvocation *)invocation
方法轉發.
3. 完整消息轉發: Normal Forwarding
與上面不同,可以理解成完整消息轉發,用來代替快速轉發做更多的事。
methodSignatureForSelector
用來生成方法簽名,這個簽名就是給 forwardInvocation
中的參數 NSInvocation
調用的。通過NSInvocation
中的SEL來執行下面的轉發邏輯.
-
NSInvocation 的內部結構:
屏幕快照 2019-01-25 上午10.18.15.png
1.methodSignatureForSelector這個方法中,如果沒有找到方法對應的實現,就會返回一個空的方法簽名,最終NSObject找不到SEL。系統就會報開頭我們提到的
unrecognized selector sent to instance
錯誤,最終導致程序報錯崩潰。
2.所以我們需要做的是自己新建方法簽名,再在forwardInvocation中用你要轉發的那個對象調用這個對應的簽名,這樣也實現了消息轉發。
上圖中methodSignature為nil
,導致-[NSObject(NSObject) doesNotRecognizeSelector:] 報錯,引起程序崩潰.
三、總結
OC方法的調用通過消息發送的形式實現,當方法的實現找不到的情況下,運行時環境會依次進行下面三個階段的查找:
第一階段:
- (BOOL)resolveInstanceMethod:(SEL)name(實例方法)
- (BOOL)resolveClassMethod:(SEL)name(類方法)
第二階段:
- (id)forwardingTargetForSelector:(SEL)aSelector(快速轉發)
在此方法中另外返回一個類的對象,該類含有對應方法的實現,runtime會在新類的方法列表中進行查找,找到就去執行,找不到依然會報錯.
第三階段:
- (void)forwardInvocation:(NSInvocation *)invocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
methodSignatureForSelector中實現方法簽名, forwardInvocation中根據methodSignatureForSelector返回的方法簽名進行消息的轉發.
參考鏈接:
http://www.lxweimin.com/p/2fd4b930588e
http://www.lxweimin.com/p/1bde36ad9938