美文网首页
关于Runtime 消息发送机制的延伸

关于Runtime 消息发送机制的延伸

作者: pengxiaochao | 来源:发表于2020-03-07 11:33 被阅读0次

    说到OC 不得不说一下OC 的消息转发机制;
    何为OC 的消息转发机制;其实就是这样的;

    [objc func] 等同于 objc_msgSend(objc,@selector(func))
    

    Objc 在向一个对象发送消息时,Runtime 库会根据对象的isa 指针找到对象实际所属的类,然后在该类的方法列表以及其父类方法列表中寻找方法运行,如果在最顶层的父类中依然找不到相映的方法时,程序在运行的时候会崩溃并抛出异常 unrecognized selector sent to xxxx,在这之前Runtime 会给出三次机会拯救程序崩溃的机会;

    1. 第一次 实现 +resolveInstanceMethod: 或者+resolveClassMethod: 方法,拯救程序
    2. 第二次 实现-forwardingTargetForSelector: 或者 + forwardingTargetForSelector:方法,拯救程序
    3. 第三次,程序首先会发送-methodSignatureForSelector 消息获得函数的参数和返回值类型,如果-methodSignatureForSelector 返回nil,RunTime 则会发出 - doesNotRecognizeSelector:消息,程序会挂掉,如果返回一个签名函数,Runtime 就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象;

    具体逻辑示意图,可看此图


    三次找不到方法的逻辑图

    这里举例说明

    创建一个Student对象,如下

    
    #import <Foundation/Foundation.h>
    
    @interface Student : NSObject
    
    -(void)instance_doSomething1;
    +(void)class_doSomething1;
    
    -(void)instance_doSomething2;
    +(void)class_doSomething2;
    
    -(void)instance_doSomething3;
    +(void)class_doSomething3;
    
    @end
    
    //----------
    #import "Student.h"
    
    @implementation Student
    @end
    

    我们在一个viewController调用 Student 的对象方法和类方法,分别分三次情况去解救程序,让程序免于崩溃;

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Student *studentOBJ = [Student new];
        /* 分别调用实例方法和类方法使程序崩溃*/
        /* 在第一步解救程序*/
        [studentOBJ instance_doSomething1];
        [Student class_doSomething1];
    
        /* 在第二步解救程序*/
        [studentOBJ instance_doSomething2];
        [Student class_doSomething2];
    
        /* 在第三步解救程序*/
        [studentOBJ instance_doSomething3];
        [Student class_doSomething3];
    }
    
    

    1.第一次解决的机会 resolveInstanceMethod

    给实例添加方法

    /* 解救找不到方法的第一次机会 (实例)*/
    +(BOOL)resolveInstanceMethod:(SEL)sel{
    
        if (sel == @selector(instance_doSomething1)) {
            class_addMethod(self, sel, (IMP)customerInstancePlayMusic, "v@:");
            return YES;
        }
        return  [super resolveInstanceMethod:sel];
    }
    
    /*  我这里起个新名字,实现了Student 对象崩溃的 instancePlayMusic 方法 */
    void customerInstancePlayMusic(id self,SEL _cmd){
         NSLog(@"Instance implementation instance_doSomething1");
    }
    
    
    /*运行结果*/
    runtime[54671:2198521] Instance implementation instance_doSomething1
    
    

    给类添加方法

    /* 解救找不到方法的第一次机会 (实例)*/
    +(BOOL)resolveClassMethod:(SEL)sel{
        if (sel == @selector(class_doSomething1)) {
            NSString *className = NSStringFromClass([self class]);
            Class metaClass = objc_getMetaClass("Student");
            class_addMethod(metaClass, sel, (IMP)customerClassPlayMusic, "v@:");
            return YES;
        }
        return  [super resolveClassMethod:sel];
    }
    
    void customerClassPlayMusic(id self,SEL _cmd){
         NSLog(@"Class implementation class_doSomething1");
    }
    
    /*运行结果*/
    runtime[54829:2212228] Class implementation class_doSomething1
    

    2.第二次解救程序的机会

    假如第一次没有实现,或者说觉得在程序中添加很多这样的分类方法,并不完美,想把崩溃的方法统一给一个类处理,然后埋点顺便报告给服务端;可以尝试一下第二次解救的机会
    创建一个工具类,我们就给它命名 为StudentTool类吧,定义如下

    #import <Foundation/Foundation.h>
    @interface StudentTool : NSObject
    @end
    //---------------------
    #import "StudentTool.h"
    
    @implementation StudentTool
    
    /* 实现了Student 的实例2 和类方法2 */
    -(void)instance_doSomething2{
        NSLog(@" StudentTool 实现了实例 方法 instance_doSomething2");
    }
    
    +(void)class_doSomething2{
        NSLog(@" StudentTool 实现了类 方法 class_doSomething2");
    }
    
    
    /* 实现了Student 的实例3方法和类方法3 */
    -(void)instance_doSomething3{
        NSLog(@" StudentTool 实现了实例 方法 instance_doSomething3 xxxx");
    }
    
    +(void)class_doSomething3{
        NSLog(@" StudentTool 实现了类 方法 class_doSomething3 xxxx");
    }
    @end
    
    

    Student+AvoidCrash 中的代码实现如下,可以在 forwardingTargetForSelector 方法中将消息转发给别的类(这里 以 StudentTool类)实现-(void)instance_doSomething2{}+(void)class_doSomething2{}不存在的方法,从而避免崩溃;

    消息转发解决对象方法的崩溃

    /* 消息转发解决实例崩溃*/
    
    /* 第二步*/
    /* 实例方法的消息转发*/
    -(id)forwardingTargetForSelector:(SEL)aSelector
    {
        return [StudentTool new];
    }
    
    /*运行结果*/
    runtime[55832:2296221]  StudentTool 实现了实例 方法 instance_doSomething2
    

    消息转发解决类方法的崩溃

    /* 类方法的消息转发*/
    + (id)forwardingTargetForSelector:(SEL)aSelector
    {
        return [StudentTool class];
    }
    /*运行结果*/
    //程序不在崩溃,方法转发到类  StudentTool 类的 同名方法中
    runtime[55832:2296221]  StudentTool 实现了类 方法 class_doSomething2
    
    

    3.第三次解救程序免于崩溃的情况

    -forwardInvocation:在一个对象无法识别消息之前调用,再需要重载-methodSignatureForSelector:,因为在调用-forwardInvocation:之前是要把消息打包成NSIvocation对象的,所以需要-methodSignatureForSelector:重载,如果不能在此方法中不能不为空的NSMethodSignature对象,程序依然会崩溃;

    Student+AvoidCrash.m中代码实现如下

    /* 第一、二步没有实现,通过第三步来解决程序异常崩溃*/
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(instance_doSomething3)) {
            NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
            if (!methodSignature) {
                /* 如果 StudentTool 这个类能相映这个 崩溃方法,就交给 StudentTool 来响应*/
                if ([StudentTool instancesRespondToSelector:aSelector]) {
                    methodSignature = [StudentTool instanceMethodSignatureForSelector:aSelector];
                }
            }
            return methodSignature;
        }
        return nil;
    }
    
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        if (anInvocation.selector == @selector(doSomething3:)) {
            StudentTool *instance = [StudentTool new];
            if ([instance respondsToSelector:anInvocation.selector]) {
                [anInvocation invokeWithTarget:instance];
            }
        }
    }
    
    

    如果有不清楚的地方,可以查看github源码 https://github.com/hunter858/runtime

    相关文章

      网友评论

          本文标题:关于Runtime 消息发送机制的延伸

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