筆者翻譯自iOS Developer Library Message Forwarding
消息轉(zhuǎn)發(fā)
向一個對象發(fā)送一個它不能處理的消息是錯誤的莱褒。然而屉来,在宣布這個錯誤前,運行時系統(tǒng)將給這個接收者對象第二次處理消息的機會。
轉(zhuǎn)發(fā)
如果你向一個對象發(fā)送一個它不能處理的消息鸦采,在宣布一個錯誤前篙骡,運行時系統(tǒng)將向該對象發(fā)送一條 forwardInvocation:
消息本辐,并帶有一個 NSInvocation
對象作為它的唯一參數(shù)——NSInvocation
對象封裝了原始的消息和它傳過來的參數(shù)。
你能實現(xiàn) forwardInvocation:
方法向消息提供一個默認的響應医增,或者通過一些方式去避免發(fā)生錯誤慎皱。就像它的名字的字面意義一樣,forwardInvocation:
通常被用作向另一個對象轉(zhuǎn)發(fā)消息叶骨。
來看看轉(zhuǎn)發(fā)的意圖茫多,想象接下來的情景:假設(shè),起初忽刽,你正在設(shè)計一個能夠響應 negotiate
消息的對象天揖,并且你希望它的響應能包含另一類對象的響應。你能夠很容易地實現(xiàn)它跪帝,通過在你實現(xiàn)的 negotiate
方法的代碼某處將 negotiate
消息傳給其他對象今膊。
進一步考慮這種情況,猜想你希望你的對象對 negotiate
消息的響應僅僅成為在其他類中已經(jīng)實現(xiàn)了的 negotiate
響應伞剑。 完成這個目標的一種方式是使你的類從其他類繼承該方法斑唬。但是,這種方式不可能總能成功黎泣。這里可能對于“為什么你的類在繼承層次結(jié)構(gòu)上要和實現(xiàn)了 negotiate
方法的類處于不同的分支”有一個好的理由恕刘。
即使你的類不能繼承 negotiate
方法,你仍然可以實現(xiàn)一個簡單地傳遞消息到其他類的實例的方法抒倚,去“實現(xiàn)”它:
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
這種方式稍微有點笨重褐着,尤其是如果你的對象有大量函數(shù)都需要向其他對象傳遞。你必須為每一個你想從其他類引入的方法實現(xiàn)一個新的方法去覆蓋它托呕。再者含蓉,如此實現(xiàn)將不可能處理你不知道的情況,在你寫代碼的時候项郊,你已經(jīng)確定了可能想要轉(zhuǎn)發(fā)的的消息的全集馅扣。但這個集合可能依靠運行時的事件,它可能改變?yōu)樵趯韺崿F(xiàn)的新的方法和類呆抑。
第二種可能是通過 forwardInvocation:
消息岂嗓,它對上面的問題提供了一個小的有組織性的解決方案,而且它是動態(tài)的而不是靜態(tài)的鹊碍。它的運行過程如下:當一個對象因為沒有能夠匹配消息中選擇器的方法厌殉,而不能響應一個消息時食绿,運行時系統(tǒng)會向該對象發(fā)送一個 forwardInvocation:
消息去通知它。每個對象都從 NSObject
類繼承了 forwardInvocation:
方法公罕。然而器紧,NSObject
版本的方法只是簡單地調(diào)用了 doesNotRecognizeSelector:
方法。通過重寫 NSObject
的方法并按你自己的想法實現(xiàn)楼眷,你能利用 forwardInvocation:
方法把消息轉(zhuǎn)發(fā)給其他對象铲汪。
為了轉(zhuǎn)發(fā)一個消息,forwardInvocation:
方法需要做的事有:
- 決定消息應該去哪
- 把它和它起初的參數(shù)一起發(fā)送過去
消息能夠通過 invokeWithTarget:
方法被發(fā)送:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
被轉(zhuǎn)發(fā)的消息的返回值會返回給起初的發(fā)送者罐柳。所有類型的返回值都能傳給發(fā)送者掌腰,包括 id,結(jié)構(gòu)體和雙精度浮點數(shù)张吉。
forwardInvocation:
方法扮演了不能識別消息的分發(fā)中心齿梁,將它們打包并發(fā)送給不同的接收者“褂迹或者它是一個傳輸站勺择,發(fā)送所有的消息到同一個目標。它能把一個消息轉(zhuǎn)換成另一個伦忠,或者僅僅“吃掉”一些消息省核,以便這里沒有響應和錯誤。forwardInvocation:
能使多個消息都對應一個響應昆码。forwardInvocation:
的功能如何取決它如何實現(xiàn)气忠。然而,它提供的在轉(zhuǎn)發(fā)鏈中鏈接對象的特性未桥,打通了程序設(shè)計的可能笔刹。
提示:只有在接收者不能調(diào)用一個存在的方法時,
forwardInvocation:
才能處理消息冬耿。比如:如果你希望你的對象轉(zhuǎn)發(fā)negotiate
消息到其他對象,它就不能有自己的negotiate
方法萌壳。如果它有亦镶,消息將不能到達forwardInvocation:
。
參考 Foundation 框架的 NSInvocation
類特性袱瓮,獲取更多有關(guān)轉(zhuǎn)發(fā)和調(diào)用的信息缤骨。
轉(zhuǎn)發(fā)和多繼承
轉(zhuǎn)發(fā)模仿繼承,并且經(jīng)常給 Objec-C 程序帶來多繼承的效果尺借。如圖所示绊起,一個對象通過轉(zhuǎn)發(fā)消息來響應它看起來就像是引入或繼承了另一個類中已定義的方法。
在例圖中燎斩,一個 Warrior
類的實例向一個 Diplomat
類的實例轉(zhuǎn)發(fā)了 negotiate
消息虱歪。Warrior
調(diào)用 negotiate
看起來就跟 Diplomat
調(diào)用一樣蜂绎。它看起來響應了 negotiate
消息,而且實際上笋鄙,它確實響應了(雖然起作用的其實是 Diplomat
對象)师枣。
轉(zhuǎn)發(fā)消息的對象因此“繼承“了來自繼承層次結(jié)構(gòu)的兩個分支(它自己和響應消息的對象)的方法。在上面的例子中萧落,看起來就像是 Warrior
類繼承了 Diplomat
和它自己的父類践美。
轉(zhuǎn)發(fā)提供了大部分你通常想通過多繼承實現(xiàn)的特性。然而找岖,它們之間也有著重要的區(qū)別:多繼承將不同的的功能結(jié)合到一個單一的對象中陨倡,它趨向于單一的,多層次的對象许布。另一反面兴革,轉(zhuǎn)發(fā)將不同的責任分配給不同的對象,它分解問題為小對象爹脾,但是用這種方式把這些對象聯(lián)系起來對消息發(fā)送者來說是透明的帖旨。
代理對象
轉(zhuǎn)發(fā)不僅僅模仿多繼承,它也使得開發(fā)能代表或“覆蓋”更具實質(zhì)性的對象的輕量級對象成為可能灵妨。代理對象代表著其他對象解阅,并且把消息過濾給它們。
在 Object-C Programming Language 的"Remote Messaging"中討論的代理(proxy)是這樣一個代理對象泌霍。它關(guān)心把消息轉(zhuǎn)發(fā)給遠端接收者的管理信息货抄,比如確保參數(shù)值通過連接被復制和被恢復等等。但它不嘗試去做其他事朱转,它不能復制遠端對象的函數(shù)功能蟹地,而僅僅是給遠端對象一個本地地址,通過該地址藤为,它能收到在另一個應用中的消息怪与。
其他類型的代理也是可能的。比如缅疟,假設(shè)你有一個操作大量數(shù)據(jù)的對象——或許它要創(chuàng)建一個復雜的圖片分别,或者它要讀一個在磁盤上的文件的內(nèi)容。建立這個對象將會是耗時的存淫,因此你更愿意懶惰地去完成它——當它確實被需要的時候或系統(tǒng)資源臨時空閑的時候耘斩。但同時,你需要至少一個代表該對象的占位符桅咆,以便應用中的其他對象能夠正常運行括授。
在這種情況下,你能夠初始化創(chuàng)建它,不需要完全成型的對象荚虚,而僅僅為它使用一個輕量級的代理薛夜。這個對象能夠獨立完成一些事情,比如回答關(guān)于數(shù)據(jù)的問題曲管,但是通常它持有一個大對象的地址却邓,并且當時機成熟時,向大對象轉(zhuǎn)發(fā)消息院水。當代理對象的 forwardInvocation:
方法收到一個指向其他對象的消息腊徙,它將確保對象是存在的,如果不存在將創(chuàng)建對象檬某。所有大對象的消息都要通過代理撬腾。因此,就程序的其他部分而言恢恼,代理對象和大對象是一樣的民傻。
轉(zhuǎn)發(fā)和繼承
盡管轉(zhuǎn)發(fā)模仿繼承,但是 NSObject
類從沒有搞混它們场斑。類似 respondsToSelector:
和 isKindOfClass:
的方法僅僅會在繼承的層次結(jié)構(gòu)上查找漓踢,不會通過轉(zhuǎn)發(fā)鏈。例如:如果 Warrior
對象被詢問是否能響應 negotiate
消息:
<pre><code>if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
</code></pre>
結(jié)果為 NO漏隐,盡管它能收到 negotiate
消息喧半,并且通過轉(zhuǎn)發(fā)給 Diplomat
,不會發(fā)生錯誤青责,也能響應它挺据。
在多數(shù)情況下,NO 就是正確答案脖隶。但它可能會不是扁耐。如果你利用轉(zhuǎn)發(fā)建立了一個代理對象或者擴展類的功能,轉(zhuǎn)發(fā)機制在繼承層次上是透明的产阱。如果你想你的對象看起來像是真的繼承了它轉(zhuǎn)發(fā)的消息的行為婉称,你需要重寫 respondsToSelector:
和 isKindOfClass:
方法去支持你的轉(zhuǎn)發(fā)算法:
- (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:
方法也應該反應到轉(zhuǎn)發(fā)算法构蹬。如果協(xié)議被使用酿矢,conformsToProtocol:
方法同樣應該被加入到修改列表中。類似的怎燥,如果一個對象轉(zhuǎn)發(fā)它收到的任何遠端消息,它應該被重寫 methodSignatureForSelector:
方法以便能返回對最終能響應被轉(zhuǎn)發(fā)消息的方法的精確描述蜜暑。例如:如果一個對象能夠向它的代理轉(zhuǎn)發(fā)消息铐姚,你應該向下面一樣實現(xiàn) methodSignatureForSelector:
:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
你可以考慮將轉(zhuǎn)發(fā)算法放到一個私有代碼里,并且所有這些方法(包括 forwardInvocation:
)將調(diào)用它。
提示:這是一項先進的技術(shù)隐绵,僅僅在遇到別無他法的時候才應該去使用之众。它的目的不是為了代替繼承。如果你必須使用這項技術(shù)依许,請先確信你已經(jīng)完全理解了轉(zhuǎn)發(fā)類和接收轉(zhuǎn)發(fā)的類的行為棺禾。
本節(jié)提到的方法在 Foundation 框架說明里的 NSObject
類特性中有詳細描述。參考 Foundation 框架說明的 NSInvocation
類特性峭跳,了解更多有關(guān) invokeWithTarget:
方法的信息膘婶。