美文网首页
iOS18-Runtime学习笔记(一)

iOS18-Runtime学习笔记(一)

作者: echo海猫 | 来源:发表于2020-04-19 16:12 被阅读0次

iOS 防Crash进行成员方法异常处理

简述:方法执行的流程为:首先在方法执行之前,系统会通过isa指针找到这个类(每个类都有个isa指针,这个isa指针相当于一个类的成员变量),然后在类中回去遍历它的方法列表,去找对应的方法编号(SEL),然后找到对应的方法编号后,通过函数指针(IMP:方法的映射关系)去找到函数执行体,进行方法的调用(如果没找到方法,会沿着父类一直找下去,如果最后没找到,就会抛出异常)

isa:NSObject 声明了一个成员变量: isa. 由于 NSObject 是所有类的根类,所以所有的对象
都会有一个 isa 的成员变量[公共继承].而该 isa 变量指向该对象的类,类在Objective-C中
也是一个实体, 由于存在Objective-C 运行环境所有的类将有自己的存储空间.Objective-C 
运行环境将为每个类分配空间. 这里 所说的 isa,正是指向这样一个类的空间. 从而建立类和对象
之间的对应关系.] 类空间 包含了该类定义的成员变量,以及方法实现, 还包含了指向自己父类空间
的指针.

SEL:方法选择器,括号内,为方法编号

IMP:一个函数指针,也可以说是对象的一个方法映射关系,可以通过方法编号去找到方法执行体,从
而达到消息发送的机制
例:int(*p)(int, int);
函数返回值类型 (* 指针变量名) (函数参数列表);
Runtime消息转发机制:
消息转发机制需经历的三个阶段:
先经过动态方法解析,返回YES,处理成功;如果返回NO,则去进行消息快速转发,如果有备用的接受
者,则返回备用的消息接收者,如果找不到继续采用慢速转发,进行方法签名,再进行消息转发,
如果没有实现,则会进行最后的抛异常的阶段
1、动态方法解析: 
+(**BOOL**)resolveInstanceMethod:(**SEL**)sel

2、消息快速转发:
//返回一个备用的接受者
-(id)forwardingTargetForSelector:(**SEL**)aSelector

3、消息慢速转发:
(1)方法签名   
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
(2)消息转发
-(void)forwardInvocation:(NSInvocation *)anInvocation

4、执行异常
//抛出异常
-(void)doesNotRecognizeSelector:(SEL)aSelector{
    NSLog(@"这个%@方法还没有实现呢",NSStringFromSelector(aSelector));
}

如果采用objc_msgSend(),去进行消息转发可能会遇到的问题:

1、导入头文件:

#import <objc/runtime.h>

#import <objc/message.h>

2、修改下面配置,设置为NO


设置允许使用objc_msgSend()

3、如果无参数直接使用objc_msgSend,前两个参数必传。如果有参数,但是接收的时候为nil时,可以采用先定义原型,再进行调用,如下:

objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
//第一个参数为id 为发送消息的对象,第二个为方法列表 后面...可传所需参数,如果多参数,可以逗号分开,继续传
//person 为向谁发送消息,即为消息对象,第二个SEL为方法标号, 
objc_msgSend(person,@selector(sendMsg:),@"hello");
void (*yg_msgsend)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))objc_msgSend;
//  先定义原型才可以使用
yg_msgsend(person, @selector(sendMsg:), @"hello ");

具体的类中实现代码为:

#import "Person.h"//本类
#import <objc/message.h>
#import "Person2.h"//备用类的接收者
//备用类的接收者实现了方法 -(void)sendMsg:(NSString *)words;

@implementation Person

//-(void)sendMsg:(NSString *)words{
//    NSLog(@"i want to express - %@",words);
//}

void sendMsg(id self, SEL _cmd, NSString *words){
    NSLog(@"====== %@",words);
}

#pragma mark - 动态方法解析
//类的动态方法
+(BOOL)resolveClassMethod:(SEL)sel{
    return NO;
}

//对象的的动态方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
//判断如果实现返回yes 否则返回NO,交给下一个处理机制去处理
//    1、匹配方法
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sendMsg:"]) {
        //实现方法
//        class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types)   第一个类 传self,第二个方法编号:sel
        return  class_addMethod(self, sel, (IMP)sendMsg, "v@:@");
    }
    return NO;
}

#pragma mark - 消息快速转发
//返回一个备用的接受者
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMsg:"]) {
        return [Person2 new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

#pragma mark - 消息慢速转发
//方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMsg:"]) {
        //返回一个NSMethodSignature对象
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//NSInvocation继承树
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    //从继承树拿到这个方法
    SEL sel = [anInvocation selector];
    Person2 *person2 = [Person2 new];
    //判断替代对象是否包含该方法,如果包含,转移target,如果没有 继续走继承树
    if ([person2 respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:person2];
    }else{
        //如果没有 继续走季成书,再没有就报错抛异常了
        [super forwardInvocation:anInvocation];
    }
}

#pragma mark - 方法未实现的情况
-(void)doesNotRecognizeSelector:(SEL)aSelector{
    NSLog(@"这个%@方法还没有实现呢",NSStringFromSelector(aSelector));
}

@end

下面是我写的一个Category:直接新建一个Category,将代码复制进去应用里就能防止应用因为实例方法(成员方法)未实现导致的崩溃。建议:测试试试,多理解注释的内容

//
//  NSObject+Exception.m
//  demo-Runtime
//
//  Created by Equal on 2020/4/19.
//  Copyright © 2020 小王同学. All rights reserved.
//

#import "NSObject+Exception.h"
#import <objc/runtime.h>

//实例方法的交换 class_addMethod 无法增加类方法,请注意!!!!!!!

@implementation NSObject (Exception)

//重写load方法
+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //获取系统的快速转发消息的方法
        Method instance_method = class_getInstanceMethod([NSObject class], @selector(forwardingTargetForSelector:));
        //获取自定义防崩溃的快速转发消息的方法
        Method yg_method = class_getInstanceMethod([NSObject class], @selector(yg_forwardingTargetForSelector:));
        //做方法交换
        method_exchangeImplementations(instance_method, yg_method);
    });
}

//1、因为我们做了方法交换,那么所有的方法都会走到我们这里
-(id)yg_forwardingTargetForSelector:(SEL)aSelector{
    //因为此方法是需要返回一个备用的消息接收者,所以我们可以去创建一个类,然后再去给这个类增加一个实现的方法就可以了
    //开始实现防崩溃的代码
    //判断本类是不是有方法,有方法就直接去调用方法
    BOOL isHaveMethod = NO;
    //不管是类方法还是实例方法,有就用本身,没有就给他来一个
    if (class_getInstanceMethod([self class], aSelector)) {
        isHaveMethod = YES;
    }
    //如果没有方法就返回了
    if (!isHaveMethod) {
        //1、创建备用类
        NSString *backup_str = @"backup_Class";
        Class backup_Class = NSClassFromString(backup_str);
        //2、给备用类加方法
        //class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types) 
//可以看一下这个方法的参数,第一个为类 第二个为方法编号,第三个是一个IMP方法映射,还有一个返回值
        if (!backup_Class)
        {
           Class superClass = [NSObject class];
           //objc_allocateClassPair:Creates a new class and metaclass.
           backup_Class = objc_allocateClassPair(superClass, "backup_Class", 0);
           /// 如果类没有对应的方法,则动态添加一个
           class_addMethod(backup_Class, aSelector, (IMP)sendMsg, "v@:");
           //注册到运行时环境(可点击进去 查看类的方法)
           objc_registerClassPair(backup_Class);
           /// 把消息转发到当前动态生成类的实例上
           return [[NSClassFromString(backup_str) alloc] init];
        }
        /// 如果类没有对应的方法,则动态添加一个
        if (!class_getInstanceMethod(NSClassFromString(backup_str), aSelector)) {
            class_addMethod(backup_Class, aSelector, (IMP)sendMsg, "v@:");
        }
        /// 把消息转发到当前动态生成类的实例上
        return [[NSClassFromString(backup_str) alloc] init];
    }
    //因为方法已经做了交换,所以必须要使用你交换过后的方法yg_forwardingTargetForSelector:
    return [self yg_forwardingTargetForSelector:aSelector];
}

//"v@:@",解释v-返回值void类型,@-self指针id类型,:-SEL指针SEL类型,@-函数第一个参数为id类型
void sendMsg(id self, SEL _cmd){
    NSLog(@"=== 快检查一下,你的%@好像没有实现 ===",NSStringFromSelector(_cmd));
}

写在后面:如有错误之处麻烦指出,加深学习,谢谢!

相关文章

网友评论

      本文标题:iOS18-Runtime学习笔记(一)

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