iOS-数组防崩溃(全)

书接上回,我们前两天研究了字典(Dictionary)崩溃的处理方式以及NSException类,而OC一个极为重要的类(Array)也进入了我们的视线,在开发过程中,我们遇到的最多的崩溃之一就是数组越界。针对这个问题,今天就让我们来详细分析如何处理数组越界导致的崩溃吧。

一、不可变数组的分析(NSArray)

1、首先我们创建NSArray的类别:

#import "NSArray+NilSafe.h"
#import 
#import "NSObject+Swizzling.h"//在NSString类别中交换方法,通用类

2、在load方法中获取原方法以及替换方法,利用GCD只执行一次,防止多线程问题:

+ (void)load{
    static dispatch_once_t onceToken;
    //调用原方法以及新方法进行交换,处理崩溃问题。
    dispatch_once(&onceToken, ^{
        //越界崩溃方式一:[array objectAtIndex:1000];
        [objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndex:) withSwizzledSelector:@selector(safeObjectAtIndex:)];
        
        //越界崩溃方式二:arr[1000];   Subscript n:下标、脚注
        [objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndexedSubscript:) withSwizzledSelector:@selector(safeobjectAtIndexedSubscript:)];
    });
}
  • 重点:这里老铁们要千万注意,我们获取数组中的数据有两种方法,一种是[array objectAtIndex:1000],另一种是arr[1000],但是千万不要以为这两种方式调用的方法都是一样的(被坑过/(ㄒoㄒ)/~~),arr[1000]的调用方法是objectAtIndexedSubscript:,所以也要针对这个方法处理。我们可以从崩溃的日志里面看到,如下图:


    arr[x]越界崩溃提示.png

    在SDK中的方法如下图:


    对应的方法.png

3、在交换方法中对越界的索引处理,这里可以返回nil或者根据你的需求返回一个你想要的值:

- (instancetype)safeObjectAtIndex:(NSUInteger)index {
    // 数组越界也不会崩,但是开发的时候并不知道数组越界
    if (index > (self.count - 1)) { // 数组越界
        return nil;
    }else { // 没有越界
        return [self safeObjectAtIndex:index];
    }
}

- (instancetype)safeobjectAtIndexedSubscript:(NSUInteger)index{
    if (index > (self.count - 1)) { // 数组越界
        return nil;
    }else { // 没有越界
        return [self safeobjectAtIndexedSubscript:index];
    } 
}

二、可变数组的分析(NSMutableArray)

1、创建NSMutableArray的分类,并且导入相应文件

#import "NSMutableArray+NilSafe.h"
#import 
#import "NSObject+Swizzling.h"

2、在load方法中交换相应的方法

由于NSMutableArray相对于NSArray可以执行插入、替换、删除等操作,数组越界的情况会比NSArray更多,所以为了妥善起见,我们针对各个方法都要作相应的处理。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //1、提示移除的数据不能为空
        [self swizzleSelector:@selector(removeObject:)
         withSwizzledSelector:@selector(hdf_safeRemoveObject:)];
        
        //2、提示数组不能添加为nil的数据
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
                                withSwizzledSelector:@selector(hdf_safeAddObject:)];
        //3、移除数据越界
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(removeObjectAtIndex:)
                                withSwizzledSelector:@selector(hdf_safeRemoveObjectAtIndex:)];
        //4、插入数据越界
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(insertObject:atIndex:)
                                withSwizzledSelector:@selector(hdf_insertObject:atIndex:)];
    
        //5、处理[arr objectAtIndex:1000]这样的越界
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(objectAtIndex:) withSwizzledSelector:@selector(hdf_objectAtIndex:)];
        
        //6、处理arr[1000]这样的越界
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(objectAtIndexedSubscript:) withSwizzledSelector:@selector(safeobjectAtIndexedSubscript:)];
        
        //7、替换某个数据越界
        [objc_getClass("__NSArrayM") swizzleSelector:@selector(replaceObjectAtIndex:withObject:) withSwizzledSelector:@selector(safereplaceObjectAtIndex:withObject:)]; 

      //8、添加数据中有nil的情况,剔除掉nil
        [objc_getClass("__NSPlaceholderArray") swizzleSelector:@selector(initWithObjects:count:) withSwizzledSelector:@selector(hdf_initWithObjects:count:)];
    });
}

3、替换方法的处理

- (instancetype)hdf_initWithObjects:(const id  _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt {
    BOOL hasNilObject = NO;
    for (NSUInteger i = 0; i < cnt; i++) {
        if ([objects[i] isKindOfClass:[NSArray class]]) {
            NSLog(@"%@", objects[i]);
        }
        if (objects[i] == nil) {
            hasNilObject = YES;
            NSLog(@"%s object at index %lu is nil, it will be filtered", __FUNCTION__, i);
        }
    }
    
    // 因为有值为nil的元素,那么我们可以过滤掉值为nil的元素
    if (hasNilObject) {
        id __unsafe_unretained newObjects[cnt];
        
        NSUInteger index = 0;
        for (NSUInteger i = 0; i < cnt; ++i) {
            if (objects[i] != nil) {
                newObjects[index++] = objects[i];
            }
        }
        
        NSLog(@"%@", [NSThread callStackSymbols]);
        return [self hdf_initWithObjects:newObjects count:index];
    }
    
    return [self hdf_initWithObjects:objects count:cnt];
}


- (void)hdf_safeAddObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s can add nil object into NSMutableArray", __FUNCTION__);
    } else {
        [self hdf_safeAddObject:obj];
    }
}

- (void)hdf_safeRemoveObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s call -removeObject:, but argument obj is nil", __FUNCTION__);
        return;
    }
    
    [self hdf_safeRemoveObject:obj];
}

- (void)hdf_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject == nil) {
        NSLog(@"%s can't insert nil into NSMutableArray", __FUNCTION__);
    } else if (index > self.count) {
        NSLog(@"%s index is invalid", __FUNCTION__);
    } else {
        [self hdf_insertObject:anObject atIndex:index];
    }
}

- (id)hdf_objectAtIndex:(NSUInteger)index {
    if (self.count == 0) {
        NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
        return nil;
    }
    
    if (index > self.count) {
        NSLog(@"%s index out of bounds in array", __FUNCTION__);
        return nil;
    }
    
    return [self hdf_objectAtIndex:index];
}

- (void)hdf_safeRemoveObjectAtIndex:(NSUInteger)index {
    if (self.count <= 0) {
        NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
        return;
    }
    
    if (index >= self.count) {
        NSLog(@"%s index out of bound", __FUNCTION__);
        return;
    }
    [self hdf_safeRemoveObjectAtIndex:index];
}

// 1、索引越界 2、移除索引越界 3、替换索引越界
- (instancetype)safeobjectAtIndexedSubscript:(NSUInteger)index{
    if (index > (self.count - 1)) { // 数组越界
        NSLog(@"索引越界");
        return nil;
    }else { // 没有越界
        return [self safeobjectAtIndexedSubscript:index];
    }
    
}

- (instancetype)safereplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject{
    if (index > (self.count - 1)) { // 数组越界
        NSLog(@"移除索引越界");
        return nil;
    }else { // 没有越界
        return [self safeobjectAtIndexedSubscript:index];
    }
}

我的注释Demo:https://github.com/caiqingchong/ArrNilTest

你可能感兴趣的:(iOS-数组防崩溃(全))