【譯】runtime編程指南_06消息轉(zhuǎn)發(fā)

Message Forwarding

給一個對象發(fā)消息,如果這個對象不處理的話,那么將會產(chǎn)生一個錯誤。但是在拋出錯誤之前,runtime 系統(tǒng)給你提供了讓你處理消息的機會。

轉(zhuǎn)發(fā)

給一個對象發(fā)消息,如果這個對象不處理的話,那么將會產(chǎn)生一個錯誤。但是在拋出錯誤之前,runtime 系統(tǒng)會給這個對象發(fā)送 forwardInvocation: 消息,并帶了一個 NSInvocation 對象作為唯一參數(shù)。這個對象包含了原始的消息和參數(shù)。

你可以實現(xiàn) forwardInvocation: 來給這個消息提供一個默認的響應(yīng),或者忽略這個錯誤消息。正如這個方法名字一樣,forwardInvocation: 經(jīng)常用來轉(zhuǎn)發(fā)消息到其他對象。

為了看到轉(zhuǎn)發(fā)的意圖的范圍,設(shè)想一下下邊的場景:首先,設(shè)想一下,你設(shè)計了一個類可以響應(yīng) negotiate 消息。你想讓他響應(yīng)這個消息的同時也包含其他類型對象的響應(yīng)。你可以很容易的在 negotiate 的實現(xiàn)中把這個消息傳遞給其他對象。

在想一下,如果你想讓你的對象響應(yīng)這個消息結(jié)果正好是在其他類中實現(xiàn)的結(jié)果,有一個方法就是讓你的類繼承于哪個類,然而,這樣處理并不是很好。你的類,和實現(xiàn)了 negotiate 的類是繼承鏈中不同的分支。

即使你的類不能繼承 negotiate 方法,但你可以通過把這個消息傳遞給另一個類的實例對象來借用這個方法。

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

這樣做的話,不夠靈活,尤其是你這個對象有大量的消息想轉(zhuǎn)發(fā)給另外一個類的對象的時候。你必須實現(xiàn)一個方法來覆蓋你想要從其他類借用過來的方法。此外,在你寫代碼的時候,很可能不知道所有要轉(zhuǎn)發(fā)的消息。這些類和方法可能在運行時被修改,應(yīng)該依賴于運行時事件。

forwardInvocation: 提供了第二個機會來讓你提供一個不太臨時的動態(tài)而不是靜態(tài)的解決方案。就像這樣一樣:當一個對象由于無法找到消息中選擇器所對應(yīng)的方法而無法響應(yīng)這個消息的時候,runtime 系統(tǒng)將會給這個對象發(fā)送 forwardInvocation: 消息。 每一個繼承于 NSObject 的類都會有這個方法。但是在 NSObject 中只是簡單地調(diào)用了 doesNotRecognizeSelector:,通過重寫這個方法來利用 forwardInvocation: 提供的機會來把消息轉(zhuǎn)發(fā)到其他對象。

轉(zhuǎn)發(fā)消息,forwardInvocation: 應(yīng)該這么做:

  • 確定消息想要去哪
  • 帶上原生參數(shù),發(fā)送消息

消息可以通過 invokeWithTarget: 來發(fā)送

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

轉(zhuǎn)發(fā)的消息的返回值將會返回到消息的調(diào)用者,所有類型的數(shù)據(jù)都可以傳遞給發(fā)送者,包括 id 類型,結(jié)構(gòu)體類型,雙精度浮點數(shù)。

forwardInvocation: 作為無法識別消息的轉(zhuǎn)發(fā)中心,把他們轉(zhuǎn)發(fā)到不同的消息接受者。它就像一個中轉(zhuǎn)站,把所有的消息轉(zhuǎn)移到同樣的位置。它可以把一個消息轉(zhuǎn)發(fā)到另一個,也可以簡單地把無法響應(yīng)的錯誤信息吞掉。forwardInvocation: 可以把幾個消息轉(zhuǎn)化為一個單獨的響應(yīng)。forwardInvocation: 是最高的實現(xiàn)者。 這個方法提供的在消息轉(zhuǎn)發(fā)鏈中鏈接對象的方式,大大提升了程序的可設(shè)計性。

只有當不執(zhí)行這個方法的時候,forwardInvocation: 才會去處理這個消息,如果你想把 negotiate 轉(zhuǎn)發(fā)到另個一對象,如果你實現(xiàn)了這個方法,那 forwardInvocation: 永遠不會被調(diào)用。

更多關(guān)于轉(zhuǎn)發(fā)和調(diào)用的信息,請看 NSInvocation

繼承和轉(zhuǎn)發(fā)

繼承轉(zhuǎn)發(fā),在 OC 中的多重繼承是很有用的,在下圖中,一個對象響應(yīng)一個信息就像借用或者繼承在其他類中定義的方法實現(xiàn)一樣。

在這張圖中,Warrior 類的實例對象轉(zhuǎn)發(fā)消息 negotiateDiplomat 類的實例對象,戰(zhàn)士會像外交官一樣談判,似乎他在響應(yīng) negotiate 消息(其實是 Diplomat 在做所有的工作)。

這個對象轉(zhuǎn)發(fā)了一個消息,因此從兩個層級關(guān)系分別 [繼承] 了方法 --- 他自己的分支,和這個對象應(yīng)答消息的分支,上邊的例子就像 Warrior 類繼承了他的父類,又繼承了 Diplomat 類一樣。

轉(zhuǎn)發(fā)提供了你想要從多重繼承得到的大多數(shù)特征。然而,兩個有很重要的不同:多重繼承把很多功能集成到了一個對象上,使其變成一個大而全的對象。轉(zhuǎn)發(fā)則是把不同的任務(wù)分配個不同的對象,它把問題分解到小的對象上,在某種程度上,就像聯(lián)合所有的對象公開的消息發(fā)送者。

代理對象

轉(zhuǎn)發(fā)不僅模仿了對象繼承,也使得開發(fā)一些輕量級的對象來代替那些繁重的對象成為可能。

Objective-C 語言中的遠程傳遞消息中描述了這個代理。代理負責消息轉(zhuǎn)發(fā)到遠端的接受者,同時確保參數(shù)被正確的復制和糾正消息鏈,等等。但他并不試圖做其他的東西,它不會去復制遠程對象的功能,而是為遠端對象提供一個可以在其他應(yīng)用接受消息的本地地址。

其他類型的代理對象也是可以的。例如,你有一個對象來處理的大量的數(shù)據(jù),可能他創(chuàng)建了一個復雜的圖像,或者是從磁盤讀取文件。這些對象的操作是很耗時的,你最好當真正需要或者系統(tǒng)空閑的時候,在去處理它。同時你也要有一個默認值,以便在這個應(yīng)用的其他對象可以正常調(diào)用它。

在這種情況下,你可以初始化但并不使它具有全部功能,它自己可以干一些事,比如響應(yīng)一些數(shù)據(jù)請求,但是大部分的情況是,在合適的時間把消息轉(zhuǎn)發(fā)到它持有的大的對象上邊,當代理對象的 forwardInvocation: 方法第一次收到轉(zhuǎn)向另一個對象的消息時,它將確保這個對象是存在的,如果不存在,則創(chuàng)建它。所有消息都會通過這個代理對象,對于程序的其他而言,代理對象和大對象是一樣的。

轉(zhuǎn)發(fā)和繼承

盡管轉(zhuǎn)發(fā)模仿了繼承,但是 NSObject 是不混淆二者的。像 respondsToSelector:isKindOfClass: 這樣的方法只是在繼承鏈中而不是在轉(zhuǎn)發(fā)鏈中。例如,一個戰(zhàn)士對象被詢問是否響應(yīng) negotiate 消息,

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

結(jié)果是 NO,盡管他可以毫無錯誤的處理這個消息,在這種場景下,將會轉(zhuǎn)發(fā)到外交官類。

在大多數(shù)情況下,返回 NO 是正確的。但是也有可能不是正確的,如果你想用轉(zhuǎn)發(fā)來建立代理對象,或者擴展一個類的功能,轉(zhuǎn)發(fā)機制就變得和繼承機制一樣了。如果你想讓你的對象表現(xiàn)的和它真的繼承于一個可以轉(zhuǎn)發(fā)消息的對象一樣的話,你要重新實現(xiàn) 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: 也應(yīng)反映轉(zhuǎn)發(fā)算法。如果協(xié)議被使用,conformsToProtocol: 同樣會被添加到響應(yīng)鏈中。同樣的,如果一個對象轉(zhuǎn)發(fā)了任何它收到的消息,它需要有自己的 methodSignatureForSelector: 方法實現(xiàn),用來準確的返回方法描述,這個方法最終將會響應(yīng)被轉(zhuǎn)發(fā)的消息。

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

你可以考慮把轉(zhuǎn)發(fā)程序作為私有方法,可以調(diào)動包含 forwardInvocation: 的所以方法。

這時一種高級技術(shù),只有在沒有其他解決辦法的時候才使用這種解決方案。用它來代替繼承是沒有意義的,如果你必須使用這些技術(shù),確保你完全理解類的轉(zhuǎn)發(fā)行為和你要轉(zhuǎn)發(fā)的類。

在這部分提到的方法在 NSObject 中有介紹, 查看NSInvocation來了解更多關(guān)于 invokeWithTarget: 的信息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容