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

動態(tài)方法解析和轉(zhuǎn)發(fā)
Message Forward.jpg

在上面的例子中,如果 foo沒有找到會發(fā)生什么?通常情況下,程序會在運(yùn)行時掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前,Objective-C 的運(yùn)行時會給你三次拯救程序的機(jī)會:

  1. Method resolution
  2. Fast forwarding
  3. Normal forwarding
Method Resolution

首先,Objective-C 運(yùn)行時會調(diào)用 +resolveInstanceMethod:
或者 +resolveClassMethod:,讓你有機(jī)會提供一個函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)并返回 YES, 那運(yùn)行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。還是以 foo
為例,你可以這么實(shí)現(xiàn):

void fooMethod(id obj, SEL _cmd)
 { 
      NSLog(@"Doing foo");
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
     if(aSEL == @selector(foo:))
     { 
            class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:"); return YES; 
     } 
    return [super resolveInstanceMethod];
}

如果 resolve 方法返回 NO ,運(yùn)行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)

PS:iOS 4.3 加入很多新的 runtime 方法,主要都是以 imp 為前綴的方法,比如 imp_implementationWithBlock()用 block 快速創(chuàng)建一個 imp 。 上面的例子可以重寫成:

IMP fooIMP = imp_implementationWithBlock(^(id _self) { NSLog(@"Doing foo");
});
class_addMethod([self class], aSEL, fooIMP, "v@:"); 

Fast forwarding

如果目標(biāo)對象實(shí)現(xiàn)了 -forwardingTargetForSelector: ,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機(jī)會。

- (id)forwardingTargetForSelector:(SEL)aSelector
{ 
      if(aSelector == @selector(foo:))
      { 
          return alternateObject; 
      } 
      return [super forwardingTargetForSelector:aSelector];
}

只要這個方法返回的不是 nil 和 self,整個消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個對象。否則,就會繼續(xù) Normal Fowarding 。

Normal forwarding

這一步是 Runtime 最后一次給你挽救的機(jī)會。首先它會發(fā)送 -
methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果 -methodSignatureForSelector:返回 nil ,Runtime 則會發(fā)出 -doesNotRecognizeSelector:消息,程序這時也就掛掉了。
如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送 -forwardInvocation:消息給目標(biāo)對象。

NSInvocation 實(shí)際上就是對一個消息的描述,包括selector 以及參數(shù)等信息。所以你可以在 -forwardInvocation:
里修改傳進(jìn)來的 NSInvocation 對象,然后發(fā)送 -invokeWithTarget:
消息給它,傳進(jìn)去一個新的目標(biāo):

- (void)forwardInvocation:(NSInvocation *)invocation
{ 
        SEL sel = invocation.selector; 
        if([alternateObject respondsToSelector:sel]) 
        { 
            [invocation invokeWithTarget:alternateObject]; 
        } 
        else 
        {
           [self doesNotRecognizeSelector:sel]; 
        }
}

Cocoa 里很多地方都利用到了消息傳遞機(jī)制來對語言進(jìn)行擴(kuò)展,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來作為代理轉(zhuǎn)發(fā)消息的;NSUndoManager 截取一個消息之后再發(fā)送;而 Responder Chain 保證一個消息轉(zhuǎn)發(fā)給合適的響應(yīng)者。

總結(jié)

Objective-C 中給一個對象發(fā)送消息會經(jīng)過以下幾個步驟:

  1. 在對象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實(shí)現(xiàn)代碼;

  2. 如果沒有找到,Runtime 會發(fā)送 +resolveInstanceMethod:或者 +resolveClassMethod:嘗試去 resolve 這個消息;

  3. 如果 resolve 方法返回 NO,Runtime 就發(fā)送 -forwardingTargetForSelector:允許你把這個消息轉(zhuǎn)發(fā)給另一個對象;

  4. 如果沒有新的目標(biāo)對象返回, Runtime 就會發(fā)送 -methodSignatureForSelector:和 -forwardInvocation:消息。你可以發(fā)送 -invokeWithTarget:消息來手動轉(zhuǎn)發(fā)消息或者發(fā)送 -doesNotRecognizeSelector:拋出異常。

轉(zhuǎn)載:http://tech.glowing.com/cn/objective-c-runtime/

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,074評論 0 9
  • Objective-C 擴(kuò)展了 C 語言,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制。而這個擴(kuò)展...
    Zsz丶少閱讀 344評論 0 0
  • Objective-C 是一個動態(tài)語言,可以通過運(yùn)行時系統(tǒng)來動態(tài)得創(chuàng)建類和對象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)。 在Objec...
    大衛(wèi)石閱讀 280評論 0 0
  • Objective-C 擴(kuò)展了 C 語言,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制。而這個擴(kuò)展...
    RobinYu閱讀 1,569評論 2 4
  • 消息轉(zhuǎn)發(fā)三部曲: 接上面消息發(fā)送,如果當(dāng)前類和父類中都沒有找到實(shí)現(xiàn),那么就會開始嘗試動態(tài)方法解析。 動態(tài)方法解析 ...
    s_在路上閱讀 2,253評論 2 14

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