美文网首页Swift进阶iOS Developer编程知识点
iOS 消息转发机制(VN的逃生之路)

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

作者: 丨n水瓶座菜虫灬 | 来源:发表于2017-03-31 11:16 被阅读705次

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

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

    消息转发.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

    相关文章

      网友评论

        本文标题:iOS 消息转发机制(VN的逃生之路)

        本文链接:https://www.haomeiwen.com/subject/ukvmottx.html