美文网首页ios 学习iOS开发指南
iOS-数组防崩溃(全)

iOS-数组防崩溃(全)

作者: woniu | 来源:发表于2018-02-02 14:47 被阅读1048次

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

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

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

    #import "NSArray+NilSafe.h"
    #import <objc/runtime.h>
    #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 <objc/runtime.h>
    #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];
        }
    }
    

    最后,感谢黄仪标大神,在我分析中大量参考和使用了他的代码。同时,我也对其中一些有缺陷的地方做了补充和注释(如:补充了arr[1000]这种越界处理)。下面同时奉上黄仪标以及我自己注释过的代码供大家参考,如果本人有理解错误或者表述不清的地方,欢迎大家随时指出来。O(∩_∩)O~~
    黄仪标runtimeDemo:https://github.com/CoderJackyHuang/RuntimeDemo
    我的注释Demo:https://github.com/caiqingchong/ArrNilTest

    相关文章

      网友评论

        本文标题:iOS-数组防崩溃(全)

        本文链接:https://www.haomeiwen.com/subject/wpxezxtx.html