Runtime之動(dòng)態(tài)方法解析和轉(zhuǎn)發(fā)

前言

在Objective-C中,如果只在頭文件中聲明了方法,但沒有在m文件中實(shí)現(xiàn)該方法,如果調(diào)用該方法,通常情況下程序會(huì)崩潰并拋出unrecognized selector sent to instance的異常。

而在異常拋出之前,Runtime會(huì)給你三次拯救程序的機(jī)會(huì):

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

下圖是動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)的圖

10.png

1. Method resolution

Runtime首先給我們提供的一次機(jī)會(huì),動(dòng)態(tài)方法解析,可以讓我們在運(yùn)行時(shí)完成向特定類添加特定方法實(shí)現(xiàn)的操作。如下例子:

@interface TestClass : NSObject

- (int)print:(int)count;
+ (void)classPrint;

@end

在TestClass類中,我們聲明了一個(gè)類方法classPrint和實(shí)例方法print,但是,并沒有在m文件中實(shí)現(xiàn)他們。此時(shí)編譯器會(huì)出現(xiàn)警告

Method definition for 'print:' not found

Method definition for 'classPrint' not found

如果在代碼中調(diào)用這兩個(gè)方法,編譯不會(huì)出錯(cuò),但運(yùn)行的時(shí)候會(huì)出錯(cuò),也就是拋出unrecognized selector sent to instance的異常。

我們使用動(dòng)態(tài)方法解析來在運(yùn)行時(shí)為TestClass類添加這兩個(gè)方法的實(shí)現(xiàn),其中類方法通過resolveClassMethod來添加,實(shí)例方法通過resolveInstanceMethod來添加。

int missingPrint(id obj, SEL _cmd, int count)
{
    NSLog(@"Wow, you found a missing method and count is %d", count);
    return count  * 100;
}

void missingClassPrint(id obj, SEL _cmd)
{
    NSLog(@"調(diào)用了missingClassPrint函數(shù)");
}

@implementation TestClass

+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"調(diào)用resolveClassMethod?。?!");
    if(sel == @selector(classPrint)) {
        class_addMethod(object_getClass(self), sel, (IMP)missingClassPrint, "v@:");
        return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}


+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"調(diào)用resolveInstanceMethod?。?!");
    if(sel == @selector(print:)) {
        class_addMethod([self class], sel, (IMP)missingPrint, "I@:I");//其中v@:表示方法的參數(shù)和返回值,詳見Type Encodings
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

上面代碼中,我們定義了兩個(gè)C函數(shù),missingPrint函數(shù)用于綁定實(shí)例方法,missingClassPrint用于綁定類方法。在resolveClassMethod方法和resolveInstanceMethod方法中,通過class_addMethod方法為類動(dòng)態(tài)添加方法實(shí)現(xiàn)。

如此一來,調(diào)用classPrint和print:這兩個(gè)方法時(shí),Runtime會(huì)解析成對(duì)應(yīng)的missingClassPrint方法和missingPrint方法。

int main(int argc, const char * argv[]) 
{
    TestClass *t = [[TestClass alloc] init];
    [TestClass classPrint];//這里調(diào)用classPrint類方法
    int res = [t print:55];//這里調(diào)用print實(shí)例方法
    NSLog(@"res = %d", res);

    return 0;
}

PS 1:class_addMethod方法的第三個(gè)參數(shù),"v@:",描述的是方法的返回值和傳參,該符號(hào)的具體內(nèi)容可在Type Encodings查看

PS 2:resolveClassMethod和resolveInstanceMethod方法都是類方法,在類方法中的self,實(shí)際上是Class對(duì)象,而不是實(shí)例對(duì)象,只能調(diào)用類方法,不能調(diào)用實(shí)例方法。

2. Fast forwarding

若在第一步中,類中沒有實(shí)現(xiàn)動(dòng)態(tài)解析方法,或者在方法中返回NO,則會(huì)走到Fast forwarding這一步。

對(duì)于實(shí)例方法,需要實(shí)現(xiàn)forwardingTargetForSelector方法

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(print:)) {
        return [MessageForwarding new];//返回另外一個(gè)對(duì)象
        //return self  //返回self會(huì)導(dǎo)致到第三步Normal forwarding
    }

    return nil;
}

這里可以看到,只要捕捉到方法選擇器是print方法,則返回一個(gè)新建的MessageForwarding對(duì)象,將該消息重定向到MessageForwarding對(duì)象來處理。這里MessageForwarding類只需聲明并實(shí)現(xiàn)了print方法即可。

若想轉(zhuǎn)發(fā)類方法,則需要重寫forwardingTargetForSelector的類方法

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(classPrint)) {
        return [MessageForwarding class];//這里的返回和實(shí)例方法中不一樣
    }
    return nil;
}

這里返回的是[MessageForwarding class],因?yàn)檎{(diào)用類方法需要的是一個(gè)類對(duì)象,而不是實(shí)例對(duì)象。

3.Normal forwarding

當(dāng)前兩步都沒有實(shí)現(xiàn),或者在Fast forwarding這一步的forwardingTargetForSelector方法中返回self,都會(huì)進(jìn)入Normal forwarding這一步。

首先會(huì)發(fā)送-methodSignatureForSelector: 消息獲得函數(shù)的參數(shù)和返回類型。接著Runtime會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象。

NSInvocation 實(shí)際上就是對(duì)一個(gè)消息的描述,包括selector 以及參數(shù)等信息。

這里需要我們自己實(shí)現(xiàn)這兩個(gè)方法。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];

    if(!methodSignature){
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"I@:I"];
    }

    return methodSignature;
}

//NSInvocation由NSMethodSignature生成
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"calling forwardInvocation: method");
    MessageForwarding *mf = [MessageForwarding new];
    
    if([mf respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:mf];
    }
}

這里仍然是將消息轉(zhuǎn)發(fā)給MessageForwarding對(duì)象來處理。

總結(jié)

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

  1. 在對(duì)象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實(shí)現(xiàn)代碼;
  2. 如果沒有找到,Runtime 會(huì)發(fā)送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve 這個(gè)消息;
  3. 如果 resolve 方法返回 NO,Runtime 就發(fā)送 -forwardingTargetForSelector: 允許你把這個(gè)消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象;
  4. 如果沒有新的目標(biāo)對(duì)象返回, Runtime 就會(huì)發(fā)送 -methodSignatureForSelector:-forwardInvocation: 消息。你可以發(fā)送 -invokeWithTarget: 消息來手動(dòng)轉(zhuǎn)發(fā)消息或者發(fā)送 -doesNotRecognizeSelector: 拋出異常。

參考文章

Objective-C Runtime
Objective-C Runtime By 楊蕭玉

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

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

  • 既然是消息發(fā)送,就存在消息發(fā)送失敗的時(shí)候。那消息發(fā)送失敗之后,runtime系統(tǒng)會(huì)怎么處理呢? 通常情況下,如果消...
    frankisbaby閱讀 353評(píng)論 0 0
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,101評(píng)論 0 9
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認(rèn)知 Runtime詳解 應(yīng)用...
    Ryan___閱讀 2,018評(píng)論 1 3
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 842評(píng)論 0 2
  • 陰天/微風(fēng) 被室友的關(guān)門聲砸醒 看了眼手機(jī)也到起床的點(diǎn)了 按照以往的每一個(gè)步驟 出門前照例檢查了背包 公交車還是一...
    程言輕閱讀 299評(píng)論 0 0

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