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

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));
}
写在后面:如有错误之处麻烦指出,加深学习,谢谢!
网友评论