iOS中Crash的常规处理

193 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

异常产生

日常开发中,我们会遇到各种Crash的出现。其中较为常见的,例如:数组越界

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) NSArray *arr;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _arr = @[@"kc", @"hk", @"kd", @"cat"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    NSString *str = [self.arr objectAtIndex:4];
    NSLog(@"str:%@", str);
}

@end

-------------------------
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** __boundsFail: index 4 beyond bounds [0 .. 3]'
terminating with uncaught exception of type NSException

常规处理

解决方式也比较简单,创建一个NSArray的分类,使用MethodSwizzlingobjectAtIndex和自定义方法进行交互,在自定义方法中,增加索引判断,从而避免数组越界。

#import "NSArray+Extension.h"
#import <objc/runtime.h>

@implementation NSArray (Extension)

+ (void)initialize
{
    if (self == [NSArray class]) {
        Method method1 = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
        Method method2 = class_getInstanceMethod(self, @selector(lg_objectAtIndex:));
        method_exchangeImplementations(method1, method2);
    }
}

- (id)lg_objectAtIndex:(NSUInteger)index{
    
    if(index < self.count){
        return [self lg_objectAtIndex:index];
    }
    
    NSLog(@"数组越界:index = %lu", index);
    return nil;
}

@end
  • 被替换的objectAtIndex:方法在__NSArrayI
    • iOSFoundation框架中,类簇是一种常用的设计模式,他将一些相近的,私有的,具体的子类组合在一个实体的抽象类下面,我称这个抽象类为实体的,是因为和我们交互的接口承载者,就是这个抽象大类
    • 我们平时常用的三大类,NSStringNSArrayNSDictionary都是类簇,我们通过他们创建的对象都是其子类对象的实例化,并不是他本身的实例化

一般来说方法交换都会在load方法中进行

  • 因为load方法执行时机早,在main函数之间就会调用,并且会主动调用
  • 所有本类和分类中的load方法都会调用,父类优先于子类,本类优先于分类
  • load方法的弊端,在objc_initload_images函数中被调用,影响启动速度,让原本的懒加载类在启动时刻被迫营业。

如果使用initialize方法代替,该方法在首次接收消息时自动调用,也能达到相同的效果,并且可以保持类的懒加载特性。

initialize方法的特点:

  • initialize方法属于被动调用的方法。原则上来说,会在main函数之后触发。但也有特殊情况,当A类load中调用B类的方法,会触发B类initialize方法,此时B类initializemain函数之前执行。
  • 当子类首次接收消息时:
    • 父类、子类都有,先调用父类的,再调用子类的
    • 子类没有父类有,执行父类的方法。原则上一个类只会调用一次initialize方法,但这种情况父类的initialize会执行多次,可以通过添加if(self == [ClassName self])来进行判断
    • 父类没有子类有,执行子类的方法
  • 分类和本类都有,本类方法会被分类覆盖。多分类看文件的编译顺序,最后编译的分类中的方法会被执行。

由于分类会覆盖本类的initialize方法,所以在initialize中进行方法交互会存在一定风险。所以还是推荐load方法内部实现Method Swizzleinitialize方法初始化全局变量或者静态变量。