1. Crash
应用崩溃是影响 APP 体验的重要一环, 而崩溃定位也常常让开发者头疼。Crash的出现就是做了一些违背代码规则的操作,常见crash类型有:
- 容器越界
- 使用未初始化的变量
- 用户授权问题
- 选择器方法未定义
- 子线程刷新ui
- KVO
- 数据类型不匹配
- 内存溢出
- 野指针
- 死循环
那么该如何高效处理Crash呢?
2. 奔溃处理
2.1 选择器方法未定义
下面这个代码明显的会崩溃,因为没有logicEdu方法。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)btnAction:(UIButton *)sender {
[sender performSelector:@selector(logicEdu:)];
}
@end
那么添加下面这个方法之后还是崩溃。
- (void) logicEdu:(UIButton *)sender {
}
首先看一下堆栈。这里exception_throw是异常抛出的意思,而抛出异常的地方是+[NSObject(NSObject) instanceMethodSignatureForSelector:] 也就是慢速消息转发里面。消息发送中会通过对象的isa找到类去类的方法列表里面寻找selector,如果没有找到则去类的父类里面查找,直到找到NSObject还没有找到的话就会进入消息转发流程,
消息转发流程里面有三个补救的机会:
- 动态方法决议(添加方法)
- 快速消息转发(转发给另一个对象来处理)
- 慢速消息转发(动态签名签名一个sel)
如果这三个都没有处理,那么就会崩溃。
这里来创建一个NSObject分类来进行快速消息转发 -forwardingTargetForSelector;
@implementation NSObject (method)
- (id)forwardingTargetForSelector:(SEL)aSelector {
id result = [self forwardingTargetForSelector:aSelector];
return result;
}
@end
这里会报警告,因为分类方法名字和主类的方法名字一样,这时候就需要用到method-Swizzing进行imp交换。这里运行后发现还是崩溃的。method-Swizzing一般是在load里面实现,load在main函数之前就会被调用,并且是主动调用,但是在load写函数会影响启动速度,应该尽量不要在load中写耗时的操作。并且类和分类是懒加载的,但是如果实现了load,那么类的加载就会提前到main函数之前。影响启动速度的原因是会在底层调用更多的方法比如attchCategory等。 有的把方法交换写到initialize里面,这也是可以的,因为initialize是第一次调用方法的时候被调用的。但是不建议在initialize里面进行方法交换,因为可能不会调用方法。
+ (void)load {
Method originalMethod = class_getInstanceMethod(self, @selector(forwardingTargetForSelector:));
Method swizzleMethod = class_g