ios多播代理的本質,消息轉發

如果一個對象收到一條無法處理的消息,運行時系統會在拋出錯誤前,給該對象發送一條forwardInvocation:消息,該消息的唯一參數是個NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數。

您可以實現forwardInvocation:方法來對不能處理的消息做一些默認的處理,也可以以其它的某種方式來避免錯誤被拋出。如forwardInvocation:的名字所示,它通常用來將消息轉發給其它的對象。

關於消息轉發的作用,您可以考慮如下情景:假設,您需要設計一個能夠響應negotiate消息的對象,並且能夠包括其它類型的對象對消息的響應。 通過在negotiate方法的實現中將negotiate消息轉發給其它的對象來很容易的達到這一目的。

更進一步,假設您希望您的對象和另外一個類的對象對negotiate的消息的響應完全一致。一種可能的方式就是讓您的類繼承其它類的方法實現。 然後,有時候這種方式不可行,因爲您的類和其它類可能需要在不同的繼承體系中響應negotiate消息。

雖然您的類無法繼承其它類的negotiate方法,您仍然可以提供一個方法實現,這個方法實現只是簡單的將negotiate消息轉發給其他類的對象,就好像從其它類那兒“借”來的現一樣。如下所示:

- negotiate
{
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}

這種方式顯得有欠靈活,特別是有很多消息您都希望傳遞給其它對象時,您必須爲每一種消息提供方法實現。此外,這種方式不能處理未知的消息。當您寫下代碼時,所有您需要轉發的消息的集合也必須確定。然而,實際上,這個集合會隨着運行時事件的發生,新方法或者新類的定義而變化。

forwardInvocation:消息給這個問題提供了一個更特別的,動態的解決方案:當一個對象由於沒有相應的方法實現而無法響應某消息時,運行時系統將通過forwardInvocation:消息通知該對象。每個對象都從NSObject類中繼承了forwardInvocation:方法。然而,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:方法只有在消息接收對象中無法正常響應消息時纔會被調用。 所以,如果您希望您的對象將negotiate消息轉發給其它對象,您的對象不能有negotiate方法。否則,forwardInvocation:將不可能會被調用。

 

 



消息轉發很象繼承,並且可以用來在Objective-C程序中模擬多重繼承。如圖 5-1所示, 一個對象通過轉發來響應消息,看起來就象該對象從別的類那借來了或者”繼承“了方法實現一樣。


圖 5-1  消息轉發

Figure 5-1 Forwarding

在上圖中,Warrior類的一個對象實例將negotiate消息轉發給Diplomat類的一個實例。看起來,Warrior類似乎和Diplomat類一樣, 響應negotiate消息,並且行爲和Diplomat一樣(儘管實際上是Diplomat類響應了該消息)。

轉發消息的對象看起來有兩個繼承體系分支——自己的和響應消息的對象的。在上面的例子中,Warrior看起來同時繼承自Diplomat和自己的父類。

消息轉發提供了多重繼承的很多特性。然而,兩者有很大的不同:多重繼承是將不同的行爲封裝到單個的對象中,有可能導致龐大的,複雜的對象。而消息轉發是將問題分解到更小的對象中,但是又以一種對消息發送對象來說完全透明的方式將這些對象聯繫起來。




儘管消息轉發很“象”繼承,但它不是繼承。例如在NSObject類中,方法respondsToSelector:isKindOfClass:只會出現在繼承鏈中,而不是消息轉發鏈中。例如,如果向一個Warrior類的對象詢問它能否響應negotiate消息,

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

返回值是NO,儘管該對象能夠接收和響應negotiate。(見圖 5-1。)

大部分情況下,NO是正確的響應。但不是所有時候都是的。例如,如果您使用消息轉發來創建一個代理對象以擴展某個類的能力,這兒的消息轉發必須和繼承一樣,儘可能的對用戶透明。如果您希望您的代理對象看起來就象是繼承自它代表的對象一樣,您需要重新實現respondsToSelector:isKindOfClass:方法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        
    }
    return NO;
}

除了respondsToSelector:isKindOfClass:外,instancesRespondToSelector:方法也必須重新實現。如果您使用的是協議類,需要重新實現的還有conformsToProtocol:方法。類似地,如果對象需要轉發遠程消息,則methodSignatureForSelector:方法必須能夠返回實際響應消息的方法的描述。例如,如果對象需要將消息轉發給它所代表的對象,您可能需要如下的methodSignatureForSelector:實現:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

您也可以將消息轉發的部分放在一段私有的代碼裏,然後從forwardInvocation:調用它。

注意:  消息轉發是一個比較高級的技術,僅適用於沒有其它更好的解決辦法的情況。它並不是用來代替繼承的。如果您必須使用該技術,請確定您已經完全理解了轉發消息的類和接收轉發消息的類的行爲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章