美文网首页
关于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 消息发送机制的延伸

    说到OC 不得不说一下OC 的消息转发机制;何为OC 的消息转发机制;其实就是这样的; Objc 在向一个对象发送...

  • runtime 消息机制简析

    runtime 消息机制消息机制可以简单分为三个方面:消息发送、动态方法解析、消息转发一.消息发送oc 中所有的方...

  • runtime的实用性讲解

    runtime 概念 runtime 运行时机制,主要是消息机制。OC 中的方法调用属于消息的发送,动态调用过程,...

  • Runtime用法简要剖析

    如果看过我前面几篇关于Runtime的文章,应该知道Runtime的消息发送机制的原理是对象根据方法编号SEL去映...

  • 方法调用底层实现

    runtime怎么实现方法的调用 :消息机制,runtime系统会把方法调用转化为消息发送。即objc-msgSe...

  • RunTime

    1.使用消息发送机制创建对象,给对象发送消息 2. runTime方法交换的使用 3. KVO本质其实也是runtime

  • iOS 知识点回顾(二)

    目录 Runtime消息发送机制isMemberOfClass 和 isKindOfClassSuper 和 Se...

  • iOS 基础知识(二)

    目录 Runtime消息发送机制isMemberOfClass 和 isKindOfClassSuper 和 Se...

  • iOS 2019年2月学习记录

    消息发送与转发1.1 简介OC的消息机制是通过runtime实现的,消息发送是通过selector快速查找IMP的...

  • runtime解析及常用方法

    什么是runtime? runtime直译:运行时机制;OC发送消息的本质,就是 runtime去调用苹果底层的一...

网友评论

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

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