Message Forwarding
給一個對象發消息,如果這個對象不處理的話,那么將會產生一個錯誤。但是在拋出錯誤之前,runtime
系統給你提供了讓你處理消息的機會。
轉發
給一個對象發消息,如果這個對象不處理的話,那么將會產生一個錯誤。但是在拋出錯誤之前,runtime
系統會給這個對象發送 forwardInvocation:
消息,并帶了一個 NSInvocation
對象作為唯一參數。這個對象包含了原始的消息和參數。
你可以實現 forwardInvocation:
來給這個消息提供一個默認的響應,或者忽略這個錯誤消息。正如這個方法名字一樣,forwardInvocation:
經常用來轉發消息到其他對象。
為了看到轉發的意圖的范圍,設想一下下邊的場景:首先,設想一下,你設計了一個類可以響應 negotiate
消息。你想讓他響應這個消息的同時也包含其他類型對象的響應。你可以很容易的在 negotiate
的實現中把這個消息傳遞給其他對象。
在想一下,如果你想讓你的對象響應這個消息結果正好是在其他類中實現的結果,有一個方法就是讓你的類繼承于哪個類,然而,這樣處理并不是很好。你的類,和實現了 negotiate
的類是繼承鏈中不同的分支。
即使你的類不能繼承 negotiate
方法,但你可以通過把這個消息傳遞給另一個類的實例對象來借用這個方法。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
這樣做的話,不夠靈活,尤其是你這個對象有大量的消息想轉發給另外一個類的對象的時候。你必須實現一個方法來覆蓋你想要從其他類借用過來的方法。此外,在你寫代碼的時候,很可能不知道所有要轉發的消息。這些類和方法可能在運行時被修改,應該依賴于運行時事件。
forwardInvocation:
提供了第二個機會來讓你提供一個不太臨時的動態而不是靜態的解決方案。就像這樣一樣:當一個對象由于無法找到消息中選擇器所對應的方法而無法響應這個消息的時候,runtime
系統將會給這個對象發送 forwardInvocation:
消息。 每一個繼承于 NSObject
的類都會有這個方法。但是在 NSObject
中只是簡單地調用了 doesNotRecognizeSelector:
,通過重寫這個方法來利用 forwardInvocation:
提供的機會來把消息轉發到其他對象。
轉發消息,forwardInvocation:
應該這么做:
- 確定消息想要去哪
- 帶上原生參數,發送消息
消息可以通過 invokeWithTarget:
來發送
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
轉發的消息的返回值將會返回到消息的調用者,所有類型的數據都可以傳遞給發送者,包括 id
類型,結構體類型,雙精度浮點數。
forwardInvocation:
作為無法識別消息的轉發中心,把他們轉發到不同的消息接受者。它就像一個中轉站,把所有的消息轉移到同樣的位置。它可以把一個消息轉發到另一個,也可以簡單地把無法響應的錯誤信息吞掉。forwardInvocation:
可以把幾個消息轉化為一個單獨的響應。forwardInvocation:
是最高的實現者。 這個方法提供的在消息轉發鏈中鏈接對象的方式,大大提升了程序的可設計性。
只有當不執行這個方法的時候,
forwardInvocation:
才會去處理這個消息,如果你想把negotiate
轉發到另個一對象,如果你實現了這個方法,那forwardInvocation:
永遠不會被調用。
更多關于轉發和調用的信息,請看 NSInvocation
。
繼承和轉發
繼承轉發,在 OC
中的多重繼承是很有用的,在下圖中,一個對象響應一個信息就像借用或者繼承在其他類中定義的方法實現一樣。

在這張圖中,Warrior
類的實例對象轉發消息 negotiate
到 Diplomat
類的實例對象,戰士會像外交官一樣談判,似乎他在響應 negotiate
消息(其實是 Diplomat
在做所有的工作)。
這個對象轉發了一個消息,因此從兩個層級關系分別 [繼承] 了方法 --- 他自己的分支,和這個對象應答消息的分支,上邊的例子就像 Warrior
類繼承了他的父類,又繼承了 Diplomat
類一樣。
轉發提供了你想要從多重繼承得到的大多數特征。然而,兩個有很重要的不同:多重繼承把很多功能集成到了一個對象上,使其變成一個大而全的對象。轉發則是把不同的任務分配個不同的對象,它把問題分解到小的對象上,在某種程度上,就像聯合所有的對象公開的消息發送者。
代理對象
轉發不僅模仿了對象繼承,也使得開發一些輕量級的對象來代替那些繁重的對象成為可能。
在 Objective-C
語言中的遠程傳遞消息中描述了這個代理。代理負責消息轉發到遠端的接受者,同時確保參數被正確的復制和糾正消息鏈,等等。但他并不試圖做其他的東西,它不會去復制遠程對象的功能,而是為遠端對象提供一個可以在其他應用接受消息的本地地址。
其他類型的代理對象也是可以的。例如,你有一個對象來處理的大量的數據,可能他創建了一個復雜的圖像,或者是從磁盤讀取文件。這些對象的操作是很耗時的,你最好當真正需要或者系統空閑的時候,在去處理它。同時你也要有一個默認值,以便在這個應用的其他對象可以正常調用它。
在這種情況下,你可以初始化但并不使它具有全部功能,它自己可以干一些事,比如響應一些數據請求,但是大部分的情況是,在合適的時間把消息轉發到它持有的大的對象上邊,當代理對象的 forwardInvocation:
方法第一次收到轉向另一個對象的消息時,它將確保這個對象是存在的,如果不存在,則創建它。所有消息都會通過這個代理對象,對于程序的其他而言,代理對象和大對象是一樣的。
轉發和繼承
盡管轉發模仿了繼承,但是 NSObject
是不混淆二者的。像 respondsToSelector:
和 isKindOfClass:
這樣的方法只是在繼承鏈中而不是在轉發鏈中。例如,一個戰士對象被詢問是否響應 negotiate
消息,
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
結果是 NO
,盡管他可以毫無錯誤的處理這個消息,在這種場景下,將會轉發到外交官類。
在大多數情況下,返回 NO
是正確的。但是也有可能不是正確的,如果你想用轉發來建立代理對象,或者擴展一個類的功能,轉發機制就變得和繼承機制一樣了。如果你想讓你的對象表現的和它真的繼承于一個可以轉發消息的對象一樣的話,你要重新實現 respondsToSelector:
和 isKindOfClass:
這兩個方法來包含你自己的轉發程序。
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
作為 respondsToSelector:
和 isKindOfClass:
的附加,instancesRespondToSelector:
也應反映轉發算法。如果協議被使用,conformsToProtocol:
同樣會被添加到響應鏈中。同樣的,如果一個對象轉發了任何它收到的消息,它需要有自己的 methodSignatureForSelector:
方法實現,用來準確的返回方法描述,這個方法最終將會響應被轉發的消息。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
你可以考慮把轉發程序作為私有方法,可以調動包含 forwardInvocation:
的所以方法。
這時一種高級技術,只有在沒有其他解決辦法的時候才使用這種解決方案。用它來代替繼承是沒有意義的,如果你必須使用這些技術,確保你完全理解類的轉發行為和你要轉發的類。
在這部分提到的方法在 NSObject 中有介紹, 查看NSInvocation來了解更多關于 invokeWithTarget:
的信息。