iOS 数组异常操作的解决办法

文章目录

    • 问题的产生
    • 解决方案
    • 总结

问题的产生

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等等,都是待处理的部分。

总结

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


优化部分

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


写在最后

使用cocoaPods导入相关框架

pod 'SafeKit'

你可能感兴趣的:(iOS_技巧篇)