美文网首页首页投稿(暂停使用,暂停投稿)
iOS 关于数组、字典等数据类型异常操作crash的解决办法

iOS 关于数组、字典等数据类型异常操作crash的解决办法

作者: 小白进城 | 来源:发表于2017-12-18 15:44 被阅读207次

    问题的产生

    NSString *string = nil;
    // 不可变数组
    NSArray *array = @[string]; // 初始化中有nil对象
    // 可变数组
    NSMutableArray *array2 = [NSMutableArray array];
    [array2 addObject:string];  // 添加nil对象
    // 不可变字典
    NSDictionary *dic = @{@"key":string};
    // 可变字典
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [dic setObject:string forKey:@"key"];   // 设置nil对象
    

    上述的几个例子中,都是对数组、字典的异常操作,因为元素中都出现了nil对象,虽然我们可以在添加之前加判断去除为nil的情况,但是如果内容很多,势必会很繁琐,如果有更好的办法帮我们做完这些繁琐的事情岂不是美事?


    项目中的问题

    笔者项目中有很多下面的写法

    NSArray *array = @[string]; // 初始化中有nil对象
    NSDictionary *dic = @{@"key":string};
    

    有的添加了三目运算符,去掉了元素为nil的情况,但是开发同伴很多都是没有判断出现的异常情况,所以存在着很多隐患,为此想了一系列的解决方案,如果正好可以提供读者一些思路,想必是极好的


    解决方案一:继承

    如果项目中有父类的存在,我们可以在父类中做些文章,我们可以一些新增数据操作方法,用来过滤掉一些异常操作(比如跳过nil对象部分)


    解决方案二:分类

    方案一显然是不理想的,因为项目中可能存在多种父类,情况多变复杂,显然操作性太低

    采用分类方式,分别新增NSArray,NSDictionary等分类文件,为其新增操作方法,在方法中过滤掉异常操作


    解决方案三:运行时

    方案二较方案一有了更高的操作性,可行性,一定程度上解决了异常操作问题,但是依旧存在着不少问题,例如,
    我们添加分类后,我们以后就必须使用新增的方法来操作数据,对于之前的旧代码依旧未能作出响应,假如全部替换的话,势必会产生不小的工作量,这不是我们想看到的;
    另外,@[],@{}这种方式将不再可用,不,系统的部分操作方法都不可用,局限性还是很大的

    那么,有没有更为优雅的方式解决上述问题呢?答案是有的,就是使用我们OC强大的运行时

    基本思路:

    1、使用分类
    2、在 + (void)load;方法中捕获系统方法,替换为我们自己的方法
    3、在自己的方法中处理掉异常

    具体实现

    例子1:addObject方法添加nil对象

    我们先写一个异常操作

    NSString *string = nil;
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:string];
    

    爆出的错误提示

    错误提示

    我们可以看到,__NSArrayM对象调用了insertObject:atIndex:产生了object cannot be nil的错误,显然易见,addObject方法最终会调用insertObject:atIndex:方法,而对象不能为nil

    接下来我们来使用运行时交换方法,处理掉这种情况

    @implementation NSMutableArray (safe)
    +(void)load{
        [self swizze];  
    }
    +(void)swizze{
        Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:));
        Method new = class_getInstanceMethod(self, @selector(insertObject_safe:atIndex:));
        if (!old || !new) {
            return;
        }
        method_exchangeImplementations(old, new); // 交换方法
    }
    
    -(void)insertObject_safe:(id)anObject atIndex:(NSUInteger)index{
        if (index > self.count || !anObject) {
            return; // 过滤到异常部分
        }
        [self insertObject_safe:anObject atIndex:index];
    }
    @end
    

    将该分类导入需要的文件中,array添加对象时就不会在出现crash问题了

    例子2:数组越界

    我们使用不可变数组做例子

    NSString *string = nil;
    NSArray *array = @[@"0",@"1",@"2"];
    NSLog(@"%@",array[5]);
    

    报错情况

    报错

    对象__NSArrayI调用objectAtIndex:出现了越界

    同样的

    @implementation NSArray (safe)
    +(void)load{
        [self swizze];
    }
    +(void)swizze{
        Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
        Method new = class_getInstanceMethod(self, @selector(objectAtIndex_safe:));
        if (!old || !new) {
            return;
        }
        method_exchangeImplementations(old, new);   // 交换方法
    }
    -(id)objectAtIndex_safe:(NSUInteger)index{
        if (index>=self.count) {
            return nil; // 处理异常部分
        }
        return [self objectAtIndex_safe:index];
    }
    @end
    

    运行,输出

    输出

    我们看到,当数组越界时,仅仅是返回了 nil

    除了上述两个例子,系统中还有很多异常操作,比如数组的插入,替换,字典的setObject、字符串的操作、NSRange等等,都是待处理的部分。


    总结

    相比于在分类中新增方法,使用运行时捕获对应方法,会更优雅,我们不必再需要大张旗鼓的使用新方法替换旧项目中的系统方法,一劳永逸


    优化部分

    1、因为我们过滤了异常部分,无法定位错误,我们调试起来异常困难,为此,这种过滤方式最好仅仅在release模式下产生作用,而debug模式下依旧需要crash,这点可以使用宏来控制,也可以使用NSAssert断言来控制


    写在最后

    使用cocoaPods导入相关框架

    pod 'SafeOperation'
    

    此框架参考自框架

    pod 'SafeKit'
    

    稍作改变,加入了断言方便debug模式下调试,删除了.h文件,作用全局,仅需导入框架即可,无需其他设置

    相关文章

      网友评论

        本文标题:iOS 关于数组、字典等数据类型异常操作crash的解决办法

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