31

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

 5 years ago
source link: http://www.cocoachina.com/ios/20180917/24934.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

iMNZb2b.png!web

崩溃报错信息

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

-[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array;
-[__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 

@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]这样的取值到底调用了何种方法:

nMV7jyn.png!web

通过报错的函数,我们可以发现,数组索引调用的是objectAtIndexedSubscript:这个函数。找到了报错的函数,我们就可以通过runtime来实现函数的交换。首先,我们为NSObject写一个分类,方便我们调用交换系统和自定义的方法:

#import 

@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

@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和文章中有理解不到位或者不足的地方,请大家多多指教,蟹蟹,也祝大家新年快乐!!

作者:橘子star

链接:https://www.jianshu.com/p/1f5c3d43b587


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK