消息轉(zhuǎn)發(fā)

筆者翻譯自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ā)消息來響應它看起來就像是引入或繼承了另一個類中已定義的方法。

Forwarding.jpg

在例圖中燎斩,一個 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: 方法的信息膘婶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛀醉,隨后出現(xiàn)的幾起案子悬襟,更是在濱河造成了極大的恐慌,老刑警劉巖拯刁,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脊岳,死亡現(xiàn)場離奇詭異,居然都是意外死亡垛玻,警方通過查閱死者的電腦和手機割捅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帚桩,“玉大人亿驾,你說我怎么就攤上這事±嗜澹” “怎么了颊乘?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長醉锄。 經(jīng)常有香客問我乏悄,道長,這世上最難降的妖魔是什么恳不? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任檩小,我火速辦了婚禮,結(jié)果婚禮上烟勋,老公的妹妹穿的比我還像新娘规求。我一直安慰自己,他們只是感情好卵惦,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布阻肿。 她就那樣靜靜地躺著,像睡著了一般沮尿。 火紅的嫁衣襯著肌膚如雪丛塌。 梳的紋絲不亂的頭發(fā)上较解,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音赴邻,去河邊找鬼印衔。 笑死,一個胖子當著我的面吹牛姥敛,可吹牛的內(nèi)容都是我干的奸焙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼彤敛,長吁一口氣:“原來是場噩夢啊……” “哼与帆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臊泌,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鲤桥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渠概,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茶凳,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年播揪,在試婚紗的時候發(fā)現(xiàn)自己被綠了贮喧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡猪狈,死狀恐怖箱沦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雇庙,我是刑警寧澤谓形,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站疆前,受9級特大地震影響寒跳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竹椒,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一童太、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胸完,春花似錦书释、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锨能,卻和暖如春检激,著一層夾襖步出監(jiān)牢的瞬間肴捉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工叔收, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人傲隶。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓饺律,卻偏偏與公主長得像,于是被迫代替她去往敵國和親跺株。 傳聞我的和親對象是個殘疾皇子复濒,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容