故事背景:在德瑪西亞的戰(zhàn)場上圣絮,硝煙彌漫,紫色方英雄薇恩正在河道處戲弄一只毫無攻擊性的螃蟹聘殖,絲毫沒有感覺到附近的殺氣晨雳。突然行瑞,從草叢中冒出敵方四員大將奸腺,只聽其中一名怒吼:“德瑪西亞!!!”......
描述此故事前,先附上一張故事的整體流程圖:
這里有一個類ADCHero
, 有四個方法血久,分別是skillQ, skillW, skillE, skillR, 但是skillR方法沒有實現(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];
薇恩在面臨生命危險的時候,準備逃跑氧吐,于是準備開啟大招R 進入隱身狀態(tài)讹蘑。
調(diào)用方法
[vn skillR];// 直接運行程序,報錯: unrecognized selector sent to instance 0x170006e00 程序崩潰筑舅,因為找不到方法實現(xiàn)座慰。
在Objective-C中對象調(diào)用方法,實際上是給對象發(fā)送消息翠拣,在底層會調(diào)用objc_msgSend方法
// 第一個參數(shù)是消息的接收者; 第二個參數(shù)是要調(diào)用的方法名; 后面的參數(shù)依次是調(diào)用的方法中的參數(shù)版仔。
objc_msgSend(receiver, selector, arg1, arg2, …) :
結(jié)局一: 可是薇恩忘了自己沒有加R的技能點,使用不出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)的實現(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)實現(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技能 保護了ADC");
}
@end
運行程序,打印信息如下:
日女使用R技能減速了敵方四人松靡,并救援薇恩 順利逃走简僧。。雕欺。岛马。。阅茶。但是蛛枚,故事的情節(jié)又有變化,由于在下路對線的時候ADC薇恩和輔助日女產(chǎn)生了分歧脸哀,導致日女心里不太爽蹦浦,于是日女不大想救薇恩。結(jié)果薇恩又死了撞蜂。
在代碼中盲镶,將forwardingTargetForSelector
方法內(nèi)部修改一下:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
return [super forwardingTargetForSelector:aSelector];
}
運行程序,打印信息如下:
然而蝌诡,故事情節(jié)又有轉(zhuǎn)機溉贿,薇恩告訴日女:“如果你不救我,我就掛機浦旱!”,日女考慮到這把是自己的晉級賽宇色,于是心想:“就救他一次吧,反正救人一命勝造七級浮屠⌒洌”
在代碼中, 重寫methodSignatureForSelector
方法和forwardInvocation
方法.
// 完整的消息轉(zhuǎn)發(fā)機制
- (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];
}
}
運行程序例隆,結(jié)果如下:
最后日女抓住了最后一次機會,救下了薇恩抢蚀,薇恩為了表達感激之情镀层,讓他的王者表哥帶日女打贏了晉級賽。
總結(jié)一下皿曲,在上面的故事中唱逢,薇恩面對敵方四人埋伏,自己又沒有R技能逃亡屋休。如何使用iOS的消息轉(zhuǎn)發(fā)機制一步一步的驚險逃脫坞古。當他調(diào)用 [vn skillR]方法時,究竟做了哪些事博投。
第一階段绸贡,看自己能不能在接受到這個消息時盯蝴,使用閃現(xiàn)逃跑毅哗,如果不能,進入到第二個階段捧挺。
第二階段虑绵,看有沒有輔助在身旁替自己接受這個消息,如果有就把消息傳遞給輔助闽烙,讓輔助救援他翅睛。如果沒有,則進入第三個階段黑竞。
第三階段捕发,把消息封裝起來,告訴輔助很魂,給他最后一次機會扎酷,讓他設(shè)法處理。
對應(yīng)與消息轉(zhuǎn)發(fā)機制遏匆,iOS消息轉(zhuǎn)發(fā)分為三大階段:
- 第一階段法挨,先征詢消息接收者所屬的類,看其是否能動態(tài)添加方法幅聘,以處理當前這個無法響應(yīng)的selector, 及動態(tài)方法解析凡纳。如果運行期系統(tǒng) 第一階段執(zhí)行結(jié)束,接收者就無法再以動態(tài)新增方法的手段來響應(yīng)消息帝蒿,進入第二階段荐糜。
- 第二階段,看看有沒有其它對象(備用接收者)能處理此消息。如果有暴氏,運行期系統(tǒng)會把消息發(fā)給那個對象丛版,轉(zhuǎn)發(fā)過程結(jié)束;如果沒有偏序,則啟動完整的消息轉(zhuǎn)發(fā)機制页畦。
- 第三階段,完整的消息轉(zhuǎn)發(fā)機制研儒。運行期系統(tǒng)會把與消息有關(guān)的全部細節(jié)都封裝到NSInvocation對象中豫缨,再給接收者最后一次機會,令其設(shè)法解決當前還未處理的問題端朵。
參考資料:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runtime.html