iOS 消息转发机制(VN的逃生之路)

故事背景:在德玛西亚的战场上,硝烟弥漫,紫色方英雄薇恩正在河道处戏弄一只毫无攻击性的螃蟹,丝毫没有感觉到附近的杀气。突然,从草丛中冒出敌方四员大将,只听其中一名怒吼:“德玛西亚!!!”......

描述此故事前,先附上一张故事的整体流程图:

消息转发.png

这里有一个类ADCHero, 有四个方法,分别是skillQ, skillW, skillE, skillR, 但是skillR方法没有实现。
在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 发起了Q技能");
}

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

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

@end 

创建一个薇恩对象

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

薇恩在面临生命危险的时候,准备逃跑,于是准备开启大招R 进入隐身状态。

调用方法

[vn skillR];// 直接运行程序,报错: unrecognized selector sent to instance 0x170006e00 程序崩溃,因为找不到方法实现。

在Objective-C中对象调用方法,实际上是给对象发送消息,在底层会调用objc_msgSend方法

//  第一个参数是消息的接收者; 第二个参数是要调用的方法名; 后面的参数依次是调用的方法中的参数。
objc_msgSend(receiver, selector, arg1, arg2, …) :

结局一: 可是薇恩忘了自己没有加R的技能点,使用不出R技能,于是在敌方四人的围殴下,壮烈牺牲。(程序崩溃)

打印信息:

程序崩溃

为了不让薇恩这么快就死了,Objective-C做了一些处理(消息转发)

在薇恩没法使用R技能时,先询问薇恩,能否使用其它的技能逃跑。薇恩想:“我没有R技能,那我直接闪现逃跑吧”。情形如下。

此时会调用ADCHero类的类方法resolveInstanceMethod, 在这个方法中动态添加其它的方法。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s", __func__);
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"skillR"]) { // 如果方法名是skillR
        class_addMethod(self, sel, (IMP)skillFlash, "@:"); // 动态添加方法skillFlash, 参数一: 消息接收者;参数二: 调用的方法名;参数三:方法对应的实现地址;参数四: 类型编码。
        return YES; // 
    }

    return [super resolveInstanceMethod:sel];
}

void skillFlash() {
    NSLog(@"闪现");
}

打印信息:

结果

薇恩使用闪现极限逃生。。。 但是,故事的情节有变化,有的时候闪现是处于CD状态,也无法使用,不能够闪现逃走,vn在想:"要不找队友支援吧!",于是把消息发给了队友日女。正巧日女从附近焦急地赶过来。

在代码中,将resolveInstanceMethod的方法内部更改一下。并重写forwardingTargetForSelector方法, forwardingTargetForSelector方法内部,创建了一个辅助英雄日女,该类中有方法skillR ,并且已经实现。
+ (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 发起了Q技能");
}

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

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

- (void)skillR {
    NSLog(@"SUP 发起了R技能 保护了ADC");
}

@end

运行程序,打印信息如下:

打印信息

日女使用R技能减速了敌方四人,并救援薇恩 顺利逃走。。。。。。但是,故事的情节又有变化,由于在下路对线的时候ADC薇恩和辅助日女产生了分歧,导致日女心里不太爽,于是日女不大想救薇恩。结果薇恩又死了。

在代码中,将forwardingTargetForSelector方法内部修改一下:

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

运行程序,打印信息如下:

打印信息

然而,故事情节又有转机,薇恩告诉日女:“如果你不救我,我就挂机!”,日女考虑到这把是自己的晋级赛,于是心想:“就救他一次吧,反正救人一命胜造七级浮屠。”

在代码中, 重写methodSignatureForSelector方法和forwardInvocation方法.

// 完整的消息转发机制
- (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];
    }
}

运行程序,结果如下:

打印结果

最后日女抓住了最后一次机会,救下了薇恩,薇恩为了表达感激之情,让他的王者表哥带日女打赢了晋级赛。

总结一下,在上面的故事中,薇恩面对敌方四人埋伏,自己又没有R技能逃亡。如何使用iOS的消息转发机制一步一步的惊险逃脱。当他调用 [vn skillR]方法时,究竟做了哪些事。
第一阶段,看自己能不能在接受到这个消息时,使用闪现逃跑,如果不能,进入到第二个阶段。
第二阶段,看有没有辅助在身旁替自己接受这个消息,如果有就把消息传递给辅助,让辅助救援他。如果没有,则进入第三个阶段。
第三阶段,把消息封装起来,告诉辅助,给他最后一次机会,让他设法处理。

对应与消息转发机制,iOS消息转发分为三大阶段:

  • 第一阶段,先征询消息接收者所属的类,看其是否能动态添加方法,以处理当前这个无法响应的selector, 及动态方法解析。如果运行期系统 第一阶段执行结束,接收者就无法再以动态新增方法的手段来响应消息,进入第二阶段。
  • 第二阶段,看看有没有其它对象(备用接收者)能处理此消息。如果有,运行期系统会把消息发给那个对象,转发过程结束;如果没有,则启动完整的消息转发机制。
  • 第三阶段,完整的消息转发机制。运行期系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的问题。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容