理解訊息傳遞機制和訊息轉發機制
訊息傳遞機制
在物件上傳遞方法叫做“傳遞訊息”(pass a message)。訊息有“名稱”(name)或“選擇子”(selector),可以接受引數,而且可能還有返回值。
在Object-c中,如果向物件傳遞訊息,那就會使用動態繫結機制來決定需要呼叫的方法。在底層,所有方法都是普通的c語言函式,然而物件受到訊息之後,究竟該呼叫哪個方法則完全於執行期決定,甚至可以在程式執行時改變。
id returnValue = [someObject messageName:parameter];
上述someObject是接受者(receiver),messageName是選擇子(selector)
void objc_msgSend(id self, SEL cmd, ...)
第一個引數代表接受者,第二個引數代表選擇子,後續引數就是訊息中的那些引數
編譯器會把剛才的那個例子中的訊息轉換為如下函式:
id returnValue = objc_msgSend(someObject, @selector(messageName:),parameter);
objc_msgSend函式會依據接受者與選擇子的型別來呼叫適當的方法。為來完成此操作,該方法需要在接受者所屬的類中搜尋其“方法列表”。找到則跳到現實程式碼,否則,就沿著繼承體系繼續向上查詢,如果還沒有則執行訊息轉發
對於其他的“邊界情況”,則需要交由Objective-c執行環境的另一些函式來處理:
objc_msgSend_stret //待發送的訊息返回結構體時
objc_msgSend_fpret //訊息返回的事浮點型
objc_msgSendSuper //如果要給超類傳送訊息
訊息轉發機制
當物件接受到無法解讀的訊息後,就會啟動“訊息轉發”機制,程式設計師可由此過程高速物件應該如何處理未知訊息。
訊息轉發分為兩大階段:
第一階段先徵詢接受者,所屬的類,看其是否能動態加方法,來處理當前這個“未知選擇子”,這叫做動態方法解析。
第二階段涉及“完整的訊息轉發機制”。在第一階段之後,接受者無法再動態新增方法去響應該選擇子的訊息了。那麼在這個時候,接受者會看有沒有其他物件能處理這條訊息,若有則執行期系統會把訊息轉給那個物件。若沒有,則啟動完整的訊息轉發機制,將與訊息有關的細節封裝到NSInvocation物件中,再給接受者最後一次機會,令其設法解決當前還未處理的這條訊息。
動態方法解析
物件在受到無法解讀的訊息後,首先將呼叫其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector //處理的是類方法
返回一個Boolean型別,表示這個類是否能新增一個例項方法以處理選擇子。
使用這種辦法的前提是:相關方法的實現程式碼已經寫好,只等著執行的時候動態插在類裡面就可以了。
備援接受者
當前接受者還有第二次機會能處理未知的選擇子,在這一步中,執行期系統會問他:能不能把這條訊息轉給其他接受者來處理。與該步驟對應的處理方法如下:
- (id)forwardingTargetForSelector:(SEL)selector
方法引數代表選擇子,若當前接受者能找到備援物件,則將其返回,若找不到,就返回nil。
注意:我們無法操作經由這一步所轉發的訊息。若是想在傳送給備援接受者之前先修改訊息內容,那就得通過完整的訊息轉發機制來做了。
完整的訊息轉發
如果轉發演算法已經來到這一步的話,那麼唯一能做的就是啟用完整的訊息轉發機制了。
首先建立NSInvocation物件,把與尚未處理的那條訊息有關的全部細節都封於其中。訊息派發系統將親自出馬,將訊息指派給目標物件。此步驟會呼叫下列方法來轉發訊息:
- (void)forwardInvocation:(NSInvocation *)invocation
實現此方法時,若發現某呼叫操作不應由本類處理,則需要呼叫超類的同名方法。這樣的話,繼承體系中的每個類有機會處理此呼叫請求,直到最後丟擲異常,表明選擇子最終未能得到處理
訊息轉發全流程
resolveInstanceMethod -> forwardingTargetForSelectoer -> forwardInvocation ->訊息未能處理。
接受者在每一步中均有機會處理訊息。步驟越往後,處理訊息的代價就越大。