iOS 消息轉(zhuǎn)發(fā)機(jī)制(VN的逃生之路)

故事背景:在德瑪西亞的戰(zhàn)場上,硝煙彌漫,紫色方英雄薇恩正在河道處戲弄一只毫無攻擊性的螃蟹,絲毫沒有感覺到附近的殺氣。突然,從草叢中冒出敵方四員大將,只聽其中一名怒吼:“德瑪西亞!!!”......

描述此故事前,先附上一張故事的整體流程圖:

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

這里有一個類ADCHero, 有四個方法,分別是skillQ, skillW, skillE, skillR, 但是skillR方法沒有實(shí)現(xiàn)。
在ADCHero.h 文件

@interface ADCHero : NSObject

// Q技能
- (void)skillQ;

// W技能
- (void)skillW;

// E技能
- (void)skillE;

// R技能
- (void)skillR;

@end

在ADCHero.m文件

@implementation ADCHero

- (void)skillQ {
    NSLog(@"ADC 發(fā)起了Q技能");
}

- (void)skillW {
    NSLog(@"ADC 發(fā)起了W技能");
}

- (void)skillE {
    NSLog(@"ADC 發(fā)起了E技能");
}

@end 

創(chuàng)建一個薇恩對象

 ADCHero *vn = [[ADCHero alloc] init];

薇恩在面臨生命危險的時候,準(zhǔn)備逃跑,于是準(zhǔn)備開啟大招R 進(jìn)入隱身狀態(tài)。

調(diào)用方法

[vn skillR];// 直接運(yùn)行程序,報錯: unrecognized selector sent to instance 0x170006e00 程序崩潰,因?yàn)檎也坏椒椒▽?shí)現(xiàn)。

在Objective-C中對象調(diào)用方法,實(shí)際上是給對象發(fā)送消息,在底層會調(diào)用objc_msgSend方法

//  第一個參數(shù)是消息的接收者; 第二個參數(shù)是要調(diào)用的方法名; 后面的參數(shù)依次是調(diào)用的方法中的參數(shù)。
objc_msgSend(receiver, selector, arg1, arg2, …) :

結(jié)局一: 可是薇恩忘了自己沒有加R的技能點(diǎn),使用不出R技能,于是在敵方四人的圍毆下,壯烈犧牲。(程序崩潰)

打印信息:

程序崩潰

為了不讓薇恩這么快就死了,Objective-C做了一些處理(消息轉(zhuǎn)發(fā))

在薇恩沒法使用R技能時,先詢問薇恩,能否使用其它的技能逃跑。薇恩想:“我沒有R技能,那我直接閃現(xiàn)逃跑吧”。情形如下。

此時會調(diào)用ADCHero類的類方法resolveInstanceMethod, 在這個方法中動態(tài)添加其它的方法。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s", __func__);
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"skillR"]) { // 如果方法名是skillR
        class_addMethod(self, sel, (IMP)skillFlash, "@:"); // 動態(tài)添加方法skillFlash, 參數(shù)一: 消息接收者;參數(shù)二: 調(diào)用的方法名;參數(shù)三:方法對應(yīng)的實(shí)現(xiàn)地址;參數(shù)四: 類型編碼。
        return YES; // 
    }

    return [super resolveInstanceMethod:sel];
}

void skillFlash() {
    NSLog(@"閃現(xiàn)");
}

打印信息:

結(jié)果

薇恩使用閃現(xiàn)極限逃生。。。 但是,故事的情節(jié)有變化,有的時候閃現(xiàn)是處于CD狀態(tài),也無法使用,不能夠閃現(xiàn)逃走,vn在想:"要不找隊友支援吧!",于是把消息發(fā)給了隊友日女。正巧日女從附近焦急地趕過來。

在代碼中,將resolveInstanceMethod的方法內(nèi)部更改一下。并重寫forwardingTargetForSelector方法, forwardingTargetForSelector方法內(nèi)部,創(chuàng)建了一個輔助英雄日女,該類中有方法skillR ,并且已經(jīng)實(shí)現(xiàn)。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s", func);
return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    NSString *selectorString = NSStringFromSelector(aSelector);
    if ([selectorString isEqualToString:@"skillR"]) {
        AsistantHero *rinv = [[AsistantHero alloc] init];
        return rinv;
    }

    // 如果隊友不在身邊
    return [super forwardingTargetForSelector:aSelector];
}

AsistantHero類.h .m文件如下:

 // AsistantHero.h文件
@interface AsistantHero : NSObject

// Q技能
- (void)skillQ;

// W技能
- (void)skillW;

// E技能
- (void)skillE;

// R技能
- (void)skillR;

@end

// AsistantHero.m 文件
@implementation AsistantHero
- (void)skillQ {
    NSLog(@"SUP 發(fā)起了Q技能");
}

- (void)skillW {
    NSLog(@"SUP 發(fā)起了W技能");
}

- (void)skillE {
    NSLog(@"SUP 發(fā)起了E技能");
}

- (void)skillR {
    NSLog(@"SUP 發(fā)起了R技能 保護(hù)了ADC");
}

@end

運(yùn)行程序,打印信息如下:

打印信息

日女使用R技能減速了敵方四人,并救援薇恩 順利逃走。。。。。。但是,故事的情節(jié)又有變化,由于在下路對線的時候ADC薇恩和輔助日女產(chǎn)生了分歧,導(dǎo)致日女心里不太爽,于是日女不大想救薇恩。結(jié)果薇恩又死了。

在代碼中,將forwardingTargetForSelector方法內(nèi)部修改一下:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    return [super forwardingTargetForSelector:aSelector];
}

運(yùn)行程序,打印信息如下:

打印信息

然而,故事情節(jié)又有轉(zhuǎn)機(jī),薇恩告訴日女:“如果你不救我,我就掛機(jī)!”,日女考慮到這把是自己的晉級賽,于是心想:“就救他一次吧,反正救人一命勝造七級浮屠。”

在代碼中, 重寫methodSignatureForSelector方法和forwardInvocation方法.

// 完整的消息轉(zhuǎn)發(fā)機(jī)制
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    NSString *selectorString = NSStringFromSelector(aSelector);
    if ([selectorString isEqualToString:@"skillR"]) {
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return signature;
    }

    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s", __func__);
    AsistantHero *rinv = [[AsistantHero alloc] init];
    if ([rinv respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:rinv];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

運(yùn)行程序,結(jié)果如下:

打印結(jié)果

最后日女抓住了最后一次機(jī)會,救下了薇恩,薇恩為了表達(dá)感激之情,讓他的王者表哥帶日女打贏了晉級賽。

總結(jié)一下,在上面的故事中,薇恩面對敵方四人埋伏,自己又沒有R技能逃亡。如何使用iOS的消息轉(zhuǎn)發(fā)機(jī)制一步一步的驚險逃脫。當(dāng)他調(diào)用 [vn skillR]方法時,究竟做了哪些事。
第一階段,看自己能不能在接受到這個消息時,使用閃現(xiàn)逃跑,如果不能,進(jìn)入到第二個階段。
第二階段,看有沒有輔助在身旁替自己接受這個消息,如果有就把消息傳遞給輔助,讓輔助救援他。如果沒有,則進(jìn)入第三個階段。
第三階段,把消息封裝起來,告訴輔助,給他最后一次機(jī)會,讓他設(shè)法處理。

對應(yīng)與消息轉(zhuǎn)發(fā)機(jī)制,iOS消息轉(zhuǎn)發(fā)分為三大階段:

  • 第一階段,先征詢消息接收者所屬的類,看其是否能動態(tài)添加方法,以處理當(dāng)前這個無法響應(yīng)的selector, 及動態(tài)方法解析。如果運(yùn)行期系統(tǒng) 第一階段執(zhí)行結(jié)束,接收者就無法再以動態(tài)新增方法的手段來響應(yīng)消息,進(jìn)入第二階段。
  • 第二階段,看看有沒有其它對象(備用接收者)能處理此消息。如果有,運(yùn)行期系統(tǒng)會把消息發(fā)給那個對象,轉(zhuǎn)發(fā)過程結(jié)束;如果沒有,則啟動完整的消息轉(zhuǎn)發(fā)機(jī)制。
  • 第三階段,完整的消息轉(zhuǎn)發(fā)機(jī)制。運(yùn)行期系統(tǒng)會把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中,再給接收者最后一次機(jī)會,令其設(shè)法解決當(dāng)前還未處理的問題。

參考資料:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runtime.html

最后編輯于
?著作權(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)容

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