IOS —— Crash分析(上)

930 阅读3分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

1. Crash

应用崩溃是影响 APP 体验的重要一环, 而崩溃定位也常常让开发者头疼。Crash的出现就是做了一些违背代码规则的操作,常见crash类型有:

  • 容器越界
  • 使用未初始化的变量
  • 用户授权问题
  • 选择器方法未定义
  • 子线程刷新ui
  • KVO
  • 数据类型不匹配
  • 内存溢出
  • 野指针
  • 死循环 那么该如何高效处理Crash呢?

2. 选择器方法未定义崩溃

下面这个代码明显的会崩溃,因为没有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_getInstanceMethod(self, @selector(LSForwardingTargetForSelector:));
    method_exchangeImplementations(originalMethod, swizzleMethod);
}

然后创建一个ForwardingTarget类,来重写消息转发方法。

id newDynamicMethod(id self,SEL _cmd){
    return [NSNull null];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    class_addMethod(self.class, sel, (IMP) newDynamicMethod, "@@:");
    [super resolveInstanceMethod:sel];
    return YES;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    id result = [super forwardingTargetForSelector:aSelector];
    return result;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    id result = [super methodSignatureForSelector:aSelector];
    return result;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [super forwardInvocation:anInvocation];
}

在分类中添加静态变量target然后在load里面初始化。

static ForwardingTarget *target = nil;

这样运行后点击button就不会崩溃了,这里还可以在newDynamicMethod里面打印crash的sel。

id newDynamicMethod(id self,SEL _cmd){
    NSLog(@"%@",NSStringFromSelector(_cmd));
    return [NSNull null];
}

在这里插入图片描述