發(fā)送消息到不處理該消息的對象會發(fā)生錯誤。然而,在聲明錯誤之前,運行時系統(tǒng)給接收對象第二次機會處理該消息。
轉(zhuǎn)發(fā)
如果發(fā)送消息到不處理該消息的對象,在聲明錯誤之前,運行時給該對象發(fā)送forwardInvocation: 消息,NSInvocation 對象作為唯一參數(shù)。NSInvocation 對象封裝原始消息和需要傳遞的參數(shù)。
可以實現(xiàn) forwardInvocation:方法,提供一個默認消息響應,或者以其他方式避免錯誤。顧名思義, forwardInvocation:通常用來將消息轉(zhuǎn)發(fā)給另一個對象
為了看到轉(zhuǎn)發(fā)的范圍和目的,想象以下場景:首先假設(shè),你正在設(shè)計一個叫做negotiate的對象可以響應消息,你希望它能響應另一個對象的響應。你可以通過傳遞negotiate消息到你實現(xiàn)的negotiate方法中的另一個對象。
更近一步,假設(shè)希望對象精確的響應negotiate 消息,則需要在另一個類中實現(xiàn)。實現(xiàn)這個目標的一個方法是讓類繼承其他類的方法。然而,它不可能以這種方式安排事情。也許存在充分的理由為什么你的類和實現(xiàn)negotiate 的類在不同分支的繼承層次結(jié)構(gòu)中。
即使類不能繼承negotiate 方法,仍然可以通過實現(xiàn)一個版本的方法來“借用”它,該方法只是簡單的將信息傳遞給另一個類的實例:
<pre><code>
-(id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
</pre></code>
這種方式有點麻煩,特別是對象要傳遞大量消息到另一個對象。你必須實現(xiàn)一個方法來覆蓋每個從其他類借來的方法。此外,這種方法不能處理你不知道的情況。例如,在寫代碼的時候,你想轉(zhuǎn)發(fā)所有的消息。這取決于運行時的事件,有可能在將來作為新方法和類實現(xiàn)。
消息提供第二次機會,提供了一個相對不那么特殊的解決方案,該方案是動態(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:方法需要做的是:
確定消息要發(fā)送到哪里
發(fā)送消息原來的參數(shù)到那里
消息可以發(fā)送到 invokeWithTarget: 方法:
<pre><code>
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
</pre></code>
轉(zhuǎn)發(fā)消息的返回值返回給原始發(fā)送者。所有類型的返回值都可以傳遞給發(fā)送者,包括id,結(jié)構(gòu),雙精度浮點數(shù)。
forwardInvocation: 方法可以作為無法識別消息的分發(fā)中心,將消息打包給不同的接收者。或者可以作為中轉(zhuǎn)站,發(fā)送所有消息到相同的目的地。它可以把一個消息轉(zhuǎn)發(fā)給另一個,或簡單的“吞咽”一些消息,所有沒有響應也沒有錯誤。forwardInvocation:方法還可以合并幾個消息到一個響應上。forwardInvocation:做什么是由系統(tǒng)決定的。然而,它提供一個機會,使得轉(zhuǎn)發(fā)鏈接中的鏈接對象為程序設(shè)計提供可能。
注意:方法只有在他們不調(diào)用名義上接收者的現(xiàn)有方法的情況下才處理消息。例如,如果你希望你的對象轉(zhuǎn)發(fā)
negotiate消息到另一個對象,則對象不能有自己的negotiate方法。如果是這樣,消息永遠不會到forwardInvocation:。
關(guān)于轉(zhuǎn)發(fā)和調(diào)用的更多信息,參見基礎(chǔ)框架引用中NSInvocation 類規(guī)范。
轉(zhuǎn)發(fā)和多重繼承
轉(zhuǎn)發(fā)類似繼承,可以用來支持Objective-C 編程的多重繼承的某些效果。如圖5-1所示,一個對象通過轉(zhuǎn)發(fā)消息來響應消息,該對象似乎是借或者“繼承”另一個類中實現(xiàn)的方法。

在這幅圖中,Warrior 類的一個實例轉(zhuǎn)發(fā)negotiate 消息給Diplomat 類的實例。戰(zhàn)士好像作為一個外交官來談判。好像響應了negotiate消息,事實上,它確實響應了(盡管實際上是外交官來做這些事)。
轉(zhuǎn)發(fā)消息的對象“繼承”方法,該方法來自繼承層次結(jié)構(gòu)的兩個分支,該對象自己的分支來響應消息。在上面的例子中,看起來好像Warrior 類繼承Diplomat 。
轉(zhuǎn)發(fā)提供了大部分多重繼承的功能。然而,兩者之間有個重要的區(qū)別:多重繼承在單一對象上結(jié)合了不同的功能。它更加強大,多層面對象。另一方面,轉(zhuǎn)發(fā)分配單獨的職責給不同的對象。它將問題分解為更小的對象,但是以一種方式將對象聯(lián)合,該方式對消息發(fā)送者是透明的。
代理對象
轉(zhuǎn)發(fā)不僅模仿多重繼承,還可以開發(fā)輕量級對象代表或“覆蓋”更實質(zhì)的對象。代理代替其他對象并傳送消息給它。
Objective-C 編程語言中“遠程消息傳遞”中描述了該代理。代理負責管理消息轉(zhuǎn)發(fā)到遠程接受者,確保復制參數(shù)值和恢復鏈接,等等。但不嘗試做其他。它不與遠程對象的功能重復,只是簡單的給遠程對象一個本地地址,該地址可以在另一個應用程序中接收消息。
其他類型的代理對象也是可以的。假設(shè),如果有個對象操作大量數(shù)據(jù),它也許會創(chuàng)建一個復雜的圖片或者讀取磁盤上的文件內(nèi)容。設(shè)置該對象可能非常耗時,所以為了簡單,只有真正需要的時候或者系統(tǒng)資源暫時閑置時使用。同時,為了保證其他對象在應用程序正常運行,該對象至少需要一個占位符。
在這樣的情況下,你可以首先創(chuàng)建,不是成熟的對象,但是時一個輕量級的代理。這個對象可以做些自己的事情,比如回答數(shù)據(jù)問題,但更多的是為大對象占個位置,當時間到了轉(zhuǎn)發(fā)消息給大對象。當代理forwardInvocation: 方法首先接收一條消息傳遞給另一個對象,它將確保對象存在,如果不存在則創(chuàng)建。所有大對象的消息都是通過代理,因此,對于其余程序而言,代理和大對象是一樣的。
轉(zhuǎn)發(fā)和繼承
盡管轉(zhuǎn)發(fā)模仿繼承,NSObject類不會混淆兩者。像respondsToSelector: 方法和isKindOfClass: 方法只查看繼承層次結(jié)構(gòu),不在轉(zhuǎn)發(fā)鏈上。例如,訪問一個戰(zhàn)士對象確認是否響應談判消息。
<pre><code>
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
</pre></code>
答案是NO,盡管它可以接收談判消息,并且沒有錯誤以及通過轉(zhuǎn)發(fā)消息給外交官來響應消息(如圖5-1)。
在許多情況下,NO是正確答案。但這里也許不是。如果使用轉(zhuǎn)發(fā)來設(shè)置代理對象或擴展一個類的功能,轉(zhuǎn)發(fā)機制必須如同繼承一樣透明。如果想讓你的對象假裝它們真正繼承它們轉(zhuǎn)發(fā)消息的對象的行為,需要重新實現(xiàn)respondsToSelector: 方法和isKindOfClass: 方法,包括轉(zhuǎn)發(fā)算法。
<pre><code>
-(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;
}
</pre></code>
除了respondsToSelector: 方法和isKindOfClass: 方法,instancesRespondToSelector:方法也要反應轉(zhuǎn)發(fā)算法。如果使用協(xié)議,conformsToProtocol:方法需要添加到列表中。同樣的,如果一個對象轉(zhuǎn)發(fā)它接收到的任何遠程消息,它必須有的一個methodSignatureForSelector: 版本,該版本必須可以準確返回描述方法。例如,如果一個對象可以轉(zhuǎn)發(fā)消息給它的代理,需要實現(xiàn)如下methodSignatureForSelector: 方法:
<pre><code>
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
</pre></code>
可以考慮將轉(zhuǎn)發(fā)算法放在私有代碼處。
注意:這是一種先進技術(shù),只有在沒有其他解決方案可行的情況下才適用。它并不打算取代繼承。如果你必須使用這種技術(shù),確保你完全理解轉(zhuǎn)發(fā)類和你要轉(zhuǎn)發(fā)的類的行為。
在本章中提到的方法在基礎(chǔ)框架引用中NSObject類規(guī)范中有說明。關(guān)于 invokeWithTarget:更多信息,參閱基礎(chǔ)框架引用中的NSInvocation 類規(guī)范。