Runtime解决NSArray数组越界崩溃问题分析

项目中经常会出现数组越界的崩溃,较好的一种解决方案是通过runtime来解决。不过如果考虑不全面的话依然会出现越界崩溃情况。
下面将一步一步根据代码结果讲解:

一、数组越界崩溃日志

注意:这里会分为三种,很多人只会注意到其中一到两种。

  1. 也是最常见的,数组有大于1个数据。
    NSArray *arr = @[@"1",@"2",@"3"];
    NSString *str = [arr objectAtIndex:5];
2018-04-20 15:50:42.709566+0800 SwiftStudyFour[13453:866758] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 2]'
我不加下面这个图的话,还会有人找不到重点,😑🙂
image.png
  1. 数组只有1个数据
    NSArray *arr = @[@"1"];
    NSString *str = [arr objectAtIndex:5];
2018-04-20 16:07:58.753246+0800 SwiftStudyFour[13632:995238] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: index 5 beyond bounds [0 .. 0]'
image.png
  1. 数组中0个数据
    NSArray *arr = @[];
    NSString *str = [arr objectAtIndex:5];
2018-04-20 16:15:16.649412+0800 SwiftStudyFour[13783:1050271] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 5 beyond bounds for empty NSArray'
image.png

有这三中图可以看看出,根据数组中内容数量不同造成数组崩溃的系统交换方法也不一样。

二 runtime 获取类名
    NSArray *commonArr = [NSArray alloc];
    NSArray *arr01 = [commonArr init];
    NSArray *arr02 = [commonArr initWithObjects:@"1", nil];
    NSArray *arr03 = [commonArr initWithObjects:@"1", @"2", nil];
    NSArray *arr04 = [commonArr initWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"commonArr: %s", object_getClassName(commonArr));
    NSLog(@"arr01: %s", object_getClassName(arr01));
    NSLog(@"arr02: %s", object_getClassName(arr02));
    NSLog(@"arr03: %s", object_getClassName(arr03));
    NSLog(@"arr04: %s", object_getClassName(arr04));
2018-04-20 16:26:34.251071+0800 SwiftStudyFour[13953:1133644] commonArr: __NSPlaceholderArray
2018-04-20 16:26:34.251140+0800 SwiftStudyFour[13953:1133644] arr01: __NSArray0
2018-04-20 16:26:34.251226+0800 SwiftStudyFour[13953:1133644] arr02: __NSSingleObjectArrayI
2018-04-20 16:26:34.251384+0800 SwiftStudyFour[13953:1133644] arr03: __NSArrayI
2018-04-20 16:26:34.251537+0800 SwiftStudyFour[13953:1133644] arr04: __NSArrayI

看到打印结果是不是和在崩溃时标注的类一致。

三 方法交换解决越界崩溃

根据上述分析,如果只做如下代码的话是不能拦截所有崩溃的,哪种情况会崩溃可根据上述所说自己试验一下🙂

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
//        __NSArrayI
        Method old = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method new = class_getInstanceMethod(self, @selector(WG_objectAtIndexI:));
        if (old && new) {
            method_exchangeImplementations(old, new);
        }
    });
}

- (id)WG_objectAtIndexI:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self WG_objectAtIndexI:index];
        } @catch (NSException *exception) {
            NSLog(@"不可数组越界了");
            return nil;
        } @finally {
        }
    } else {
        return [self WG_objectAtIndexI:index];
    }
}

正确交换方法如下:

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method old = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method new = class_getInstanceMethod(self, @selector(WG_objectAtIndex_NSArrayI:));
        if (old && new) {
            method_exchangeImplementations(old, new);
        }
        
        old = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(objectAtIndex:));
        new = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(WG_objectAtIndex_NSSingleObjectArrayI:));
        if (old && new) {
            method_exchangeImplementations(old, new);
        }
        
        old = class_getInstanceMethod(objc_getClass("__NSArray0"), @selector(objectAtIndex:));
        new = class_getInstanceMethod(objc_getClass("__NSArray0"), @selector(WG_objectAtIndex_NSArray0:));
        if (old && new) {
            method_exchangeImplementations(old, new);
        }
        
    });
}

- (id)WG_objectAtIndex_NSArrayI:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self WG_objectAtIndex_NSArrayI:index];
        } @catch (NSException *exception) {
            NSLog(@"不可数组多元素越界了");
            return nil;
        } @finally {
        }
    } else {
        return [self WG_objectAtIndex_NSArrayI:index];
    }
}

- (id)WG_objectAtIndex_NSSingleObjectArrayI:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self WG_objectAtIndex_NSSingleObjectArrayI:index];
        } @catch (NSException *exception) {
            NSLog(@"不可数组一个元素越界了");
            return nil;
        } @finally {
        }
    } else {
        return [self WG_objectAtIndex_NSSingleObjectArrayI:index];
    }
}

- (id)WG_objectAtIndex_NSArray0:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self WG_objectAtIndex_NSArray0:index];
        } @catch (NSException *exception) {
            NSLog(@"不可数组0个元素越界了");
            return nil;
        } @finally {
        }
    } else {
        return [self WG_objectAtIndex_NSArray0:index];
    }
}

实验代码:

    NSArray *arr1 = @[];
    NSString *str1 = [arr1 objectAtIndex:5];
    
    NSArray *arr2 = @[@"1"];
    NSString *str2 = [arr2 objectAtIndex:5];
    
    NSArray *arr3 = @[@"1", @"2", @"3"];
    NSString *str3 = [arr3 objectAtIndex:5];

结果:不会崩溃了,打印日志如下

2018-04-20 16:47:16.717812+0800 CustomLearn[14152:1183376] 不可数组0个元素越界了
2018-04-20 16:47:16.718006+0800 CustomLearn[14152:1183376] 不可数组一个元素越界了
2018-04-20 16:47:16.718197+0800 CustomLearn[14152:1183376] 不可数组多元素越界了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容