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

這里有一個類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)");
}
打印信息:

薇恩使用閃現(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é)果如下:

最后日女抓住了最后一次機(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