iOS--再也不用担心数组越界

作者: 乐逍遥的笔记 | 来源:发表于2017-12-29 17:18 被阅读189次

最近在网易云捕上看到一些数组越界导致的崩溃日志,所以决定数组的越界做一些处理。


崩溃报错信息

在项目的开发中,笔者一般遇到的问题就是,数组越界:

  1. -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array;
  2. -[__NSArrayM objectAtIndexedSubscript:]: index 0 beyond bounds for empty array;
    很明显,这两个函数是在数组取值的时候发生的越界情况,在网上搜索了很多大神的文章,也自己总结了一下,下面罗列出两种处理方法:

一、为NSArray、NSMutableArray添加分类并添加方法

首先,我们为NSarray创建分类并添加方法,在.h文件中:

@interface NSArray (ErrorHandle)

/**
 为数组分类添加的方法  可以在应用中直接调用 可以防止数组越界导致的crash

 @param index 传入的取值下标
 @return id类型的数据
 */
- (id)objectAtIndexVerify:(NSUInteger)index;
- (id)objectAtIndexedSubscriptVerify:(NSUInteger)idx;
@end

在.m文件中,我们可以将这两个方法实现出来:

@implementation NSArray (ErrorHandle)
/**
 *  防止数组越界
 */
- (id)objectAtIndexVerify:(NSUInteger)index{
    if (index < self.count) {
        return [self objectAtIndex:index];
    }else{
        return nil;
    }
}
/**
 *  防止数组越界
 */
- (id)objectAtIndexedSubscriptVerify:(NSUInteger)idx{
    if (idx < self.count) {
        return [self objectAtIndexedSubscript:idx];
    }else{
        return nil;
    }
}

类似的,我们可以为NSMutableArray创建分类,(在可变数组中,我们插入nil对象也会产生crash,所以我们要对可变数组做特殊处理)

#import <Foundation/Foundation.h>

@interface NSMutableArray (ErrorHandle)
/**
 数组中插入数据

 @param object 数据
 @param index 下标
 */
- (void)insertObjectVerify:(id)object atIndex:(NSInteger)index;
/**
 数组中添加数据

 @param object 数据
 */
- (void)addObjectVerify:(id)object;

@end

在可变数组的.m文件中

@implementation NSMutableArray (ErrorHandle)
/**
 *  数组中插入数据
 */
- (void)insertObjectVerify:(id)object atIndex:(NSInteger)index{
    if (index < self.count && object) {
        [self insertObject:object atIndex:index];
    }
}
/**
 *  数组中添加数据
 */
- (void)addObjectVerify:(id)object{
    if (object) {
        [self addObject:object];
    }
}

特别说明:以上方法在项目的实际运用中,要想防止数组越界,就需要调用我们自己添加的方法了,而不要调用系统的了。

二、用runtime处理数组越界

不到万不得已,笔者一般是不想用runtime的。不过runtime确确实实也能解决数组越界的问题,在我们数组越界处理的第一种方法中,我们可以看见,我们无法使用索引来从数组中取值了(即类似:cell.textLabel.text = self.dataSource[indexPath.row];这样的取值方式)。那如果我们想要这种取值方式的话,就需要用runtime来实现了。

首先,我们先来确定下self.dataSource[indexPath.row]这样的取值到底调用了何种方法: 数组索引调用的方法
通过报错的函数,我们可以发现,数组索引调用的是objectAtIndexedSubscript:这个函数。找到了报错的函数,我们就可以通过runtime来实现函数的交换。首先,我们为NSObject写一个分类,方便我们调用交换系统和自定义的方法:
#import <Foundation/Foundation.h>

@interface NSObject (SwizzleMethod)

/**
 *  对系统方法进行替换(交换实例方法)
 *
 *  @param systemSelector 被替换的方法
 *  @param swizzledSelector 实际使用的方法
 *  @param error            替换过程中出现的错误消息
 *
 *  @return 是否替换成功
 */
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error;
@end

在.m文件中,我们需要将方法实现出来:

#import "NSObject+SwizzleMethod.h"
#import <objc/runtime.h>

@implementation NSObject (SwizzleMethod)

/**
 *  对系统方法进行替换
 *
 *  @param systemSelector 被替换的方法
 *  @param swizzledSelector 实际使用的方法
 *  @param error            替换过程中出现的错误消息
 *
 *  @return 是否替换成功
 */
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error{
    
    Method systemMethod = class_getInstanceMethod(self, systemSelector);
    if (!systemMethod) {
        return NO;
    }
    
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    if (!swizzledMethod) {
        
        return NO;
    }
    
    if (class_addMethod([self class], systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        
        class_replaceMethod([self class], swizzledSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
    }else{
        method_exchangeImplementations(systemMethod, swizzledMethod);
    }
    
    return YES;
}
@end

在方法交换和替换的过程中,如果被替换的方法或者我们将要使用的方法没有的话,直接ruturn,不进行方法互换,经过双重检验才进行方法的互换。
我们以NSMutableArray为例子,同样的NSMutableArray添加分类,在.h文件中只需要写下如下代码:

+(void)load{
    [super load];
    //无论怎样 都要保证方法只交换一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //交换NSMutableArray中的方法
        [objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndex:) swizzledSelector:@selector(jz_objectAtIndex:) error:nil];
        //交换NSMutableArray中的方法
        [objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(jz_objectAtIndexedSubscript:) error:nil];
    });
}

- (id)jz_objectAtIndex:(NSUInteger)index{
    if (index < self.count) {
        return [self jz_objectAtIndex:index];
    }else{
        
        NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld   %ld   %@", index, self.count, [self class]);
        return nil;
    }
}
- (id)jz_objectAtIndexedSubscript:(NSUInteger)index{
    if (index < self.count) {

        return [self jz_objectAtIndexedSubscript:index];
    }else{
        NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld   %ld   %@", index, self.count, [self class]);
        return nil;
    }
}

同样的,我们也可以在NSArray的分类中添加如下代码:

+(void)load{
    [super load];
    //无论怎样 都要保证方法只交换一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //交换NSArray中的objectAtIndex方法
        [objc_getClass("__NSArrayI") SystemSelector:@selector(objectAtIndex:) swizzledSelector:@selector(sxy_objectAtIndex:) error:nil];
        //交换NSArray中的objectAtIndexedSubscript方法
        [objc_getClass("__NSArrayI") SystemSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(sxy_objectAtIndexedSubscript:) error:nil];
    });
}

- (id)sxy_objectAtIndexedSubscript:(NSUInteger)idx{
    if (idx < self.count) {
        return [self sxy_objectAtIndexedSubscript:idx];
    }else{
        NSLog(@" 你的 NSArray数组已经越界了 但是已经帮你处理好了  %ld   %ld", idx, self.count);
        return nil;
    }
}

- (id)sxy_objectAtIndex:(NSUInteger)index{
    if (index < self.count) {
        return [self sxy_objectAtIndex:index];
    }else{
        NSLog(@" 你的 NSArray数组已经越界了 但是已经帮你处理好了  %ld   %ld", index, self.count);
        
        return nil;
    }
}

关于上面的Demo,笔者已经上传git,需要的小伙伴去下载吧!数组越界Demo

总结:以上两种方法目前用的都可行,貌似用runtime封装虽然复杂一点,但是使用起来更为隐蔽,也更自如一些,并且之前的数组取值不用做改动。大家在项目中两种方法,可以喜欢哪种用哪种了,妈妈再也不用担心我的数组越界了!!!(此处只是添加了数组取值时候的防止越界,在实际项目中可能用到的不止这几种方法(例如插入),大家可以根据自己的实际需要添加)。如果Demo和文章中有理解不到位或者不足的地方,请大家多多指教,蟹蟹,也祝大家新年快乐!!

相关文章

  • iOS--再也不用担心数组越界

    iOS--再也不用担心数组越界 iOS--再也不用担心数组越界

  • iOS--再也不用担心数组越界

    最近在网易云捕上看到一些数组越界导致的崩溃日志,所以决定数组的越界做一些处理。 在项目的开发中,笔者一般遇到的问题...

  • iOS Crash三部曲~之二常见Crash

    1 越界 1.1 数组越界 第1类崩溃就是越界问题,最常见的就是数组越界。 崩溃信息: 分析: 可以看出当前数组的...

  • 我再也不用烦你了

    我再也不用担心你突然窜到床上吓我, 我再也不用烦心你掉毛掉的到处都是, 我再也不用担心你的爪子伤了我, 我再也不用...

  • 一组图告诉你为什么要装智能锁?

    01 手指就是钥匙! 再也不用带钥匙了,再也不用担心钥匙丢了! 02 孩子不用在脖子上挂钥匙了! 不用担心出门倒垃...

  • 几年的租房生活让我加倍珍惜眼前的新房

    再也不用卧室当客厅,床当沙发了;再也不用厨房卫生间在一起了;再也不用担心涨房租了;再也不用担心房东突然一个电话房子...

  • 《再也不用担心了》

    文/端木家达 今天是我感染奥密克戎第3天,明后天一过,健康状况可能就会逐步恢复。说实话,到目前为止,我的体感并没有...

  • 如此反复直到离开

    再也不用每天担心分手了,

  • 重启践行

    停止写作践行一年的时间里,刚开始,真的是浑身轻松,再也不用担心每周的作业,再也不用担心写不出来怎么办,不用担心点评...

  • java.lang.ArrayIndexOutOfBoundsE

    名字上看是数组下标越界错误,解决方法就是查看为什么下标越界。 如下: 12代表越界数组的下标, originalD...

网友评论

  • Gwanaak:数组只有一个元素的时候,用runtime就崩溃
    Gwanaak:@橘子star @[@"语文"],这种方式创建的数组
    Gwanaak:@橘子star NSArray,我是xcode10跑的
    乐逍遥的笔记:刚试了一下,可变数组和不可变数组只有一个元素的时候,并没有崩溃。你用的NSArray还是NSMutableArray。:smile:
  • 神秘嘉宾yzj:pod search YZJRuntimeSafe试下
    乐逍遥的笔记:好的,抽空看下大神的库。:smile:
  • 41ad2fae26db:Category 那个还好,runtime这个系统调用这个函数时候也被替换掉了,有时候会导致app崩溃,系统函数崩溃,慎用哦
    乐逍遥的笔记:嗯嗯,runtime确实要慎用,一不小心会出现意想不到的问题。感谢提醒:smile:
  • 和平村村民:这有点滥用runtime了吧,数组越界就应该让他崩溃,不要掩盖问题……你能防止返回nil以后 不会有别的问题么
    乐逍遥的笔记:我这里就做了数组取值的时候,不让程序崩溃的操作。目前返回nil没问题的,但是你要是还做别的操作的话,需要另外做处理了。:smile:

本文标题:iOS--再也不用担心数组越界

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