• OC:浅析Runtime中消息转发机制


    一、介绍

    OC是一门动态性语言,其实现的本质是利用runtime机制。在runtime中,对象调用方法,其实就是给对象发送一个消息,也即objc_msgSend()。在这个消息发送的过程中,系统会进行一系列的操作,最终实现消息的成功转发或者异常的抛出。这个传递的过程就是消息的转发。

    消息转发过程:1、动态解析    2、快转发(接收者重定向)     3、慢转发(完整转发,方法重定向)  

    二、示例

    在Person类中.h文件中声明了一个吃的方法: eat ,但是.m中没有具体的实现。

    @interface Person : NSObject
    -(void)eat;
    @end

    然后在控制器中创建Person对象调用eat,发现crash了。

    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *person = [[Person alloc] init];
        [person eat];
    }

    实现结果和原因如下

    crash:  '-[Person eat]: unrecognized selector sent to instance 0x60000366f310
    reason:   methodSignatureForSelector方法中,返回了一个空的对应方法签名,最终导致程序报错崩溃

    此时,针对这种情况,消息就进入了转发流程。系统允许我们在Person中通过重写上述的方法进行消息转发。

    注意:三个阶段的方法不一定都要重写,如果上一步重写的方法完成了消息的发送,程序就结束了。

    第一阶段:动态解析,也即动态添加方法(可以是eat方法实现,也可以是其他方法的实现)

    1、在Person类中实现一个方法addEatMethod

    -(void)addEatMethod {
        NSLog(@"------addEatMethod-----------");
    }

    2、在Person类中重写resolveInstanceMethod,将addEatMethod通过class_addMethod添加给当前对象

    //动态解析:动态添加方法
    /*
     1、当前对象调用方法,在runtime中,其实就是给当前对象发送一个消息,也即objc_msgSend()。
     2、首先系统会通过当前对象的isa指针指向它的类,然后在类的方法缓存中查找是否有此方法,如果有,则取出调用,
        如果没有,就去方法列表中查找,如果有,则调用,如果也没有,接着去父类中重复之前的操作进行查找。最后都没有找到该方法时,进入消息的转发流程。
     3、此时系统提供给调用者一个机会,重写resolveInstanceMethod方法,进行动态解析,要么不处理沿继承树传递,要么去动态添加方法的实现,完成消息的发送。
     */
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSString *methodName = NSStringFromSelector(sel);
        if ([methodName isEqualToString:@"eat"]) {
            SEL  selName = NSSelectorFromString(@"addEatMethod");
            Method method = class_getInstanceMethod(self, selName);
            return class_addMethod(self, sel, method_getImplementation(method), "v@:");
        }
        return [super resolveInstanceMethod:sel];
    }

    3、程序没有崩溃,打印结果如下

    2019-09-28 17:56:33.197134+0800 消息转发[48761:2749552] ------addEatMethod-----------

    第二阶段:快转发,也即接收者重定向

     1、在另一个类中实现eat方法,例如在OtherPerson类中实现了eat方法

    #import "OtherPerson.h"
    @implementation OtherPerson
    -(void)eat {
        NSLog(@"__%s__: OtherPerson_吃饭",__func__);
    }
    @end

     2、在Person类中重写forwardingTargetForSelector方法,返回新的消息接收者

    //快转发:接收者重定向
    /*
    如果调用者重写了resolveInstanceMethod,但是并没有动态的去添加方法的实现,消息就进入快速转发阶段。
    系统提供给调用者去查找实现了该方法的其他接收者,也即重写forwardingTargetForSelector。在该方法中,调用者要么重新
    指定消息的接收者完成消息的发送,要么仍然沿继承树传递。
    */
    - (id)forwardingTargetForSelector:(SEL)aSelector {
    
        NSString *methodName = NSStringFromSelector(aSelector);
        if ([methodName isEqualToString:@"eat"]){
            return [[OtherPerson alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }

    3、程序也没有崩溃,打印结果如下

    2019-09-28 18:08:10.542697+0800 消息转发[48995:2764873] __-[OtherPerson eat]__: OtherPerson_吃饭

    第三阶段:慢转发,也即完整转发,消息重定向

    1、在Person类中重写methodSignatureForSelector手动生成方法签名,目的是验证选择器的合法性

    2、在Person类中重写forwardInvocation指定其他的接收者(也可以修改实现方法,修改响应对象)

    //慢转发:完整转发,消息重定向
    /*
    调用者在resolveInstanceMethod和forwardingTargetForSelector中都没有对消息进行处理,消息就进入慢速转发阶段。
    系统提供给调用最后一次机会处理消息,重写methodSignatureForSelector和forwardInvocation方法,
    在methodSignatureForSelector中对消息的实现进行签名,在forwardInvocation中消息接收者根据签名完成消息转发。
    */
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
        NSString *methodName = NSStringFromSelector(aSelector);
        if ([methodName isEqualToString:@"eat"]) {
            //手动生成方法签名
            //NSMethodSignature *signature = [NSMehtodSignature methodSignatureForSelector:aSelector];
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return signature; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { //获取签名信息 SEL selector = anInvocation.selector; //新建需要转发消息的对象 OtherPerson *person = [OtherPerson new]; //判断是否响应 if ([person respondsToSelector:selector]) { //若可以响应,则将消息转发给该对象处理 [anInvocation invokeWithTarget:person]; //也可以修改实现方法,修改响应对象 //selector = @selector(addEatMethod); //[anInvocation setSelector:selector]; //[anInvocation invokeWithTarget:self]; }else {
        [super forwardInvocation:anInvocation];
      } }

    3、程序仍然没有崩溃,打印结果

    2019-09-28 18:16:13.216412+0800 消息转发[49140:2773592] __-[OtherPerson eat]__: OtherPerson_吃饭

    最后:异常捕捉

    1、在Person类中重写doesNotRecognizeSelector方法。

    //慢转发:完整转发,消息重定向
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
        NSString *methodName = NSStringFromSelector(aSelector);
        if ([methodName isEqualToString:@"eat"]) {
            //手动生成方法签名
            //NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:aSelector];
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return signature; } return [super methodSignatureForSelector:aSelector]; }//消息未识别 /* 动态解析、快转发、慢转发这三个过程中调用者都没有处理消息时,系统会调用doesNotRecognizeSelector方法,抛出异常 调用者也可以重写这个方法,对异常做自己的处理操作,防止程序崩溃,提高app使用体验。 推荐用在线上版本,开发环境下,还是要把问题抛出来,以便解决。 */ -(void)doesNotRecognizeSelector:(SEL)aSelector { NSLog(@"----Person没有实现eat方法----- "); }

    2、程序依然没有崩溃,打印结果 (注意:方法签名必须要有,不然就会崩溃) 

    2019-09-28 18:24:01.136673+0800 消息转发[49320:2784505] ----Person没有实现eat方法----- 
  • 相关阅读:
    MSP430开学的序章
    此地不留爷,自有留爷处
    【javascript】继承
    【css】css3属性
    【计划】合格的前端
    【grunt】grunt 基础学习
    【防火墙】防火墙分类,过滤流程
    【js】name 与 array 的纠葛
    【TRICK】解决锚点定位向下浮动Xpx问题
    【性能】web提升性能的小总结
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/11604277.html
Copyright © 2020-2023  润新知