runtime解决iOS开发中数组越界导致crash问题

2,957 阅读2分钟

一、什么是数组越界

数组越界,很多新手容易中招的bug,

NSArray *ar = @[@"123", @(456), @"haha"];
nsstring *str = ar[3];//数组取值越界了

这个看起来很简单,但是实际开发应用中,各种数据变量在程序运行过程中,是经常不断变化的,很容易被忽视,一不小心就越界导致crash。我们先不论为什么越界,这是另一个议题,至少最基本的我们要先保证程序不会奔溃是吧。

二、数组越界最简单的一种解决方案,当然就是取值的时候限制下index的值,比如:

NSArray *moduleTexts = @[@"资讯系统", @"数字资源", @"震例系统", @"百科系统"];
int index = self.moduleCode.intValue-1;//moduleCode是服务器返回值,NSString类型
index = MAX(MIN(index, 3), 0);
lb_moduleType.text = moduleTexts[index];

三、在数组的分类中使用runtime机制来交换方法

基本思路是:当数组越界时返回nil,没有越界时返回原本的index.这样就能达到防止程序崩溃的问题.

1、创建NSObject的Category分类

此分类文件就一个方法,该方法用runtime的机制来替换系统方法。

NSObject+ExchangeMethod.h文件:

#import <Foundation/Foundation.h>
@interface NSObject (ExchangeMethod)

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

@end

创建.m文件NSObject+ExchangeMethod.m:

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

@implementation NSObject (ExchangeMethod)
+ (BOOL)exchangedSystemSelector:(SEL)systemSelector withSelector:(SEL)changedSelector error:(NSError *)error{

    Method systemMethod = class_getInstanceMethod(self, systemSelector);
    if (!systemMethod) {
        return NO;
    }

    Method changedMethod = class_getInstanceMethod(self, changedSelector);
    if (!changedMethod) {
        return NO;
    }

    if (class_addMethod([self class], systemSelector, method_getImplementation(changedMethod), method_getTypeEncoding(changedMethod))) {
        class_replaceMethod([self class], changedSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
    }else{
        method_exchangeImplementations(systemMethod, changedMethod);
    }

    return YES;
}
@end

2、创建NSArray的分类,

NSArray+Overflow.h文件:

#import <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface NSArray (Overflow)

@end

NSArray+Overflow.m文件:

#import "NSArray+Overflow.h"
#import "NSObject+ExchangeMethod.h"
#import <objc/runtime.h>

@implementation NSArray (Overflow)

+(void)load{
    [super load];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [objc_getClass("__NSArrayI") exchangedSystemSelector:@selector(objectAtIndex:) withSelector:@selector(hd_objectAtIndex:) error:nil];
        [objc_getClass("__NSArrayI") exchangedSystemSelector:@selector(objectAtIndexedSubscript:) withSelector:@selector(hd_objectAtIndexedSubscript:) error:nil];
    });
}
- (id)hd_objectAtIndexedSubscript:(NSUInteger)index{
    if (index < self.count) {
        return [self hd_objectAtIndexedSubscript:index];
    }else{
        NSLog(@"Error:数组越界了,index = %ld, count = %ld", index, self.count);
        return nil;
    }
}

- (id)hd_objectAtIndex:(NSUInteger)index{
    if (index < self.count) {
        return [self hd_objectAtIndex:index];
    }else{
        NSLog(@"Error:数组越界了,index = %ld, count = %ld", index, self.count);
        return nil;
    }
}
@end

3、创建NSMutabeArray的分类,

NSMutableArray+Overflow.h文件:

#import <Foundation/Foundation.h>

@interface NSMutableArray (Overflow)

@end

NSMutableArray+Overflow.m文件:

#import "NSMutableArray+Overflow.h"
#import "NSObject+ExchangeMethod.h"

@implementation NSMutableArray (Overflow)
+(void)load{
    [super load];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [objc_getClass("__NSArrayM") exchangedSystemSelector:@selector(objectAtIndex:) withSelector:@selector(hd_objectAtIndex:) error:nil];
        [objc_getClass("__NSArrayM") exchangedSystemSelector:@selector(objectAtIndexedSubscript:) withSelector:@selector(hd_objectAtIndexedSubscript:) error:nil];
    });
}
- (id)hd_objectAtIndexedSubscript:(NSUInteger)idx{
    if (idx < self.count) {
        return [self hd_objectAtIndexedSubscript:idx];
    }else{
        NSLog(@"Error:数组越界了,index = %ld, count = %ld", idx, self.count);
        return nil;
    }
}

- (id)hd_objectAtIndex:(NSUInteger)index{
    if (index < self.count) {
        return [self hd_objectAtIndex:index];
    }else{
        NSLog(@"Error:数组越界了,index = %ld, count = %ld", index, self.count);
        return nil;
    }
}
@end

4、最后在项目工程下XXXX-Prefix.pch文件里添加NSArray和NSMutableAray的分类头文件就行了:

#ifdef __OBJC__
#define MAS_SHORTHAND

#import "NSArray+overflow.h"
#import "NSMutableArray+Overflow.h"

#endif