iOS中的崩溃拦截 - 消息转发

server/2024/10/19 2:22:05/

引言

在iOS的开发中,线上崩溃是一件令人十分头疼的事情,每个需求的上线初期都让团队的人忐忑不安,由于崩溃率指标问题,我们团队专门做了这么一期崩溃拦截系统的需求来降低崩溃率。其中最值得分享的一则崩溃应该是未找到方法(unrecognized selector),这个崩溃在OC的项目中很常见,有一些就真的只是未实现该方法,有一些则是做类型转换时对应错误到值没有找到对应方法,还有一些比较隐晦的可能是在方法交换时交换了对方没有的方法,不管什么原因,如果没有适当的处理都会导致出现unrecognized selector的崩溃

那么该怎么处理拦截这类崩溃呢?就是消息转发机制。

消息转发机制

在该类崩溃出现之前其实我们有三个机会可以来处理它

1.动态方法解析(Dynamic Method Resolution)

+ (BOOL)resolveInstanceMethod:(SEL)sel{}+ (BOOL)resolveClassMethod:(SEL)sel{}

在这一阶段我们可以尝试在运行时为对象添加为实现的方法,并返回YES表示我们已经处理了该异常,如果我们什么都不做,该方法放回NO,就会进入消息转发阶段。

2.消息快速转发(Fast Forwarding)

- (id)forwardingTargetForSelector:(SEL)aSelector{}

在快速转发这一步中,系统首先会调用forwardingTargetForSelector:方法询问当前对象是否可以将消息转发给另一个对象,如果我们在这个方法中返回一个对象,那么就结束了消息转发流程,转到另一个对象来处理处理消息。但是如果我们返回nil,或者未处理该方法,那么消息将会进入慢速转发流程。

3.慢速转发(Normal Forwarding)

- (void)forwardInvocation:(NSInvocation *)anInvocation{}

快速转发流程失败后,系统就会进入慢速转发阶段,首先会调用methodSignatureForSelecto:方法来获取方法签名,如果返回`nil`,消息传递过程就会停止,并抛出`unrecognized selector`异常。如果提供了方法签名,系统接着会调用`forwardInvocation:`方法,你可以在这里进一步处理未识别的消息,例如记录日志或执行备用行为。

实现拦截

虽然每个阶段我们都可以进行拦截和处理,但是通常来讲我们会在消息转发的阶段进行拦截,因为消息转发机制提供了更灵活,面向对象的方法来处理未实现的方法调用,而且更容易维护,更灵活,代码可读性高。还避免了污染类的方法列表。

我们可以通过重写NSObject的forwardingTargetForSelector:方法来实现这个功能,也可以进行方法交换,这这里我们以对象方法为例,使用方法交换的方式来实现这个功能。使用方法交换而不是重写的好处是更灵活,对于不需要我们处理的方法,我们可以直接让它去执行原来的方法。

1.交换方法

创建用作方法交换的方法,这里面有个注意的一点在方法交换时为没有的方法动态添加了方法,避免交换父类的方法导致坑爹的现象,虽然在我们这个场景中不会出现,但在进行方法交换时这是一个很容易被忽视的问题。

//交换实例方法
bool cmRuntimeSwizzleMethod(Class class1,SEL selector1,Class class2, SEL selector2) {Method method1 = class_getInstanceMethod(class1, selector1);Method method2 = class_getInstanceMethod(class2, selector2);if (!method1 || !method2) {return  false;}//为class添加方法,否则可能会交换父类的方法(添加失败 就说明class已经有这个方法)class_addMethod(class1, selector1, method_getImplementation(method1), method_getTypeEncoding(method1));class_addMethod(class2, selector2, method_getImplementation(method2), method_getTypeEncoding(method2));//执行交换method_exchangeImplementations(class_getInstanceMethod(class1, selector1), class_getInstanceMethod(class2, selector2));return  true;
}+ (BOOL)cmRuntimeSwizzleSelector:(SEL)selector1 withSelector:(SEL)selector2{return cmRuntimeSwizzleMethod(self, selector1, self, selector2);
}

2.实现cmForwardingTargetForSelector:方法

实现cmForwardingTargetForSelector:进行消息转发,转发的指定的类中。

- (id)cmForwardingTargetForSelector:(SEL)selector{//判断是否需要拦截,过滤系统方法if ([self isEqual:[NSNull null]] || ![self cmOverrideForwardingMethods]) {return [[LMCrashDetector alloc] initWithInvoker:self.class selector:selector];}return [self cmForwardingTargetForSelector:selector];
}

3.判断是否需要转发

在上面的转发代码之前我们进行了一个特殊的判断,cmOverrideForwardingMethods。

该方法的作用有两个,首先当我们把消息转发到LMCrashDetector之后,由于LMCrashDetector一定也没有该方法,所以还会执行到这里,这样就构成了死循环。

还有一个问题,并不是只有我们会进行消息转发,在我们使用的三方框架中,仍然会有会许多人会交换forwardingTargetForSelector:方法,对于这种情况我们都只需要让它执行原来的forwardingTargetForSelector:方法即可。

所以需要进行如下判断:

- (BOOL)cmOverrideForwardingMethods {return (class_getMethodImplementation([NSObject class], @selector(forwardingTargetForSelector:)) !=class_getMethodImplementation([self class], @selector(forwardingTargetForSelector:))) || (class_getMethodImplementation([NSObject class], @selector(forwardInvocation:)) != class_getMethodImplementation([self class], @selector(forwardInvocation:)));
}

4.处理崩溃

而对于所有需要转发的情况,我都直接转发的LMCrashDetector类中进行处理,使用依赖注入的方式,将当前的class及SEL传递到处理类中。

/// 崩溃调用者
static Class kCrashInvoker = nil;
/// 崩溃方法
static SEL kCrashSelector = nil;- (instancetype)initWithInvoker:(Class)invoker selector:(SEL)selector{if (self = [super init]) {kCrashInvoker = invoker;kCrashSelector = selector;}return self;
}

接下来我们将实现LMCrashDetector的methodSignatureForSelector:和forwardInvocation:两个方法来处理慢速转发。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"%@-%@-%@",kCrashInvoker,NSStringFromSelector(kCrashSelector),[NSThread callStackSymbols]);
}

将所有需要的信息上报的后台,或者firebase,整个关于调用为实现方法的崩溃拦截功能就实现完成了。

结语

通过本文的介绍,我们深入探讨了如何利用消息转发机制在iOS中实现崩溃拦截。消息转发不仅为未识别的选择器提供了一种优雅的处理方式,也为开发者在调试和优化应用时提供了更多的灵活性。然而,正如我们所讨论的,虽然消息转发能帮助避免崩溃,但滥用它可能会掩盖代码中的真正问题。因此,建议开发者在实际应用中,合理使用这一机制,以保持代码的健壮性和可维护性。希望本文的内容能够为你提供有价值的参考,帮助你更好地掌控iOS开发中的异常处理。
 


http://www.ppmy.cn/server/101851.html

相关文章

vtk快捷键

vtkRenderer快捷键 按键W --- 网格显示模型按键S --- 曲面显示模型按键P --- 显示模型包围框按键F --- 放大到选取点按键R --- 重置相机视图按键A ---- 切换演员模式按键C ---- 切换相机模式按键T ---- 切换轨迹球模式按键J ----- 切换操纵杆模式

C语言日常练习 Day16

目录 一、求一个3*3的整型矩阵对角线元素之和 二、有一个已经排序好了的数组,要求输入一个数后,按原来排序的规律将它插入数组中 三、输出“魔方阵”,所谓魔方阵是指它的每一行、每一列和对角线之和均相等 一、求一个3*3的整型矩阵对角线元…

Linux:Linux环境基础开发工具使用

✨✨✨学习的道路很枯燥,希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一. Linux 软件包管理器 yum 1.1 什么是软件包 1.2 查看软件包 1.3 如何安装软件 1.4 如何卸载软件 二 Linux编辑器-vim使用 ​编辑 2.1 vim的基本概念 2.2 vim正常模式命令集…

串列翼无人机技术详解

串列翼无人机作为一种特殊布局的飞行器,其概念可追溯至早期航空探索时期。随着航空技术的不断进步,尤其是复合材料、先进控制算法及动力系统的革新,串列翼无人机逐渐从理论走向实践。这一设计初衷在于通过前后两组机翼的巧妙布局,…

Apache-JMeter压测工具教程

下载安装 《JMeter官网下载》 下载完成后,找个文件夹进行解压 配置环境变量 JAVA_HOME(如果是JAVA8还需要配置CLASSPATH)、JMETER_HOME JMETER_HOME修改bin目录下的jmeter.properties文件编码为UTF-8 5.6.3这个版本encoding已经默认为UT…

深入解析三路快排:一种高效的排序算法

在数据结构和算法的世界中,快排(Quick Sort)无疑是最受欢迎的排序算法之一。今天,探讨一种优化的快排变体——三路快排(3-Way Quick Sort),它在处理具有重复元素的数组时展现出了令人惊叹的效率…

web技术1——jdk目录结构(重要),tomcat服务器

jdk文件夹结构(重要) bin目录: 里面都是.exe可执行文件。java,javac,javadoc,java编译工具,java监测工具等.exe文件都在这里。 include目录: 底层有用c写的东西,这里面包含很多c语言的文件&#xff0c…

qt一个控件放在另外一个控件或窗体的前面或者后面

1.通过stackOver和stackUnder去控制。 // QWidget window; // window.setGeometry(100, 100, 300, 200);// QLabel *label new QLabel("在前的控件", &window); // label->setGeometry(60, 40, 180, 60);// QPushButton *button new QPushB…