最近项目上线以后,想解决下线上的bug,发现数组越界的问题还存在不少,特别空数组越界。
为了提高提高用户体验,减少崩溃,便开始想办法解决。首先想到的是,既然数组越界,那只要在从数组中取值之前对数组的长度进行判断不就可以了嘛。但是项目中从数组中取值用到的地方有很多,如果一个个地方去加的话,首先要花上大量的时间,一不小心还有漏掉某些地方。
经过查阅资料,我发现利用Method Swizzling,在运行时将自己的方法和NSArray类的方法进行交换,能够很好的解决这一问题。Method Swizzling的使用,我用了github上的第三方库JRSwizzle。
#import "NSObject+SafeCategory.h"
#import <JRSwizzle/JRSwizzle.h>
#define LOG_ERROR(err) \
do { \
if (err) { \
NSLog(@"Error: %@", err); \
err = nil; \
} \
} while(0)
@interface NSArray (SafeCategory)
- (id)objectAtSafeIndex:(NSUInteger)index;
@end
@implementation NSArray (SafeCategory)
- (id)objectAtSafeIndex:(NSUInteger)index
{
if (index > self.count - 1 || !self.count) {
@try {
return [self objectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self objectAtSafeIndex:index];
}
}
@end
@interface NSMutableArray (SafeCategory)
- (void)TKSafe_addObject:(id)anObject;
- (id)mutableObjectAtSafeIndex:(NSUInteger)index;
@end
@implementation NSMutableArray (SafeCategory)
- (void)TKSafe_addObject:(id)anObject
{
if (anObject != nil) {
[self TKSafe_addObject:anObject];
} else {
#ifdef DEBUG
NSAssert(NO, @"object is nil");
#endif
}
}
- (id)mutableObjectAtSafeIndex:(NSUInteger)index
{
if (index > self.count - 1 || !self.count) {
@try {
return [self mutableObjectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self mutableObjectAtSafeIndex:index];
}
}
@end
@implementation NSObject (SafeCategory)
+ (void)configureSafeMethods
{
NSError *error = nil;
[[[NSArray array] class] jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(objectAtSafeIndex:)
error:&error];
LOG_ERROR(error);
[[[NSMutableArray array] class] jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(mutableObjectAtSafeIndex:)
error:&error];
LOG_ERROR(error);
[[[NSMutableArray array] class] jr_swizzleMethod:@selector(addObject:)
withMethod:@selector(TKSafe_addObject:)
error:&error];
LOG_ERROR(error);
}
@end
本以为这样将NSArray和NSMutableArray类的方法都进行了交换,线上的数组越界bug应该再也不会出现了吧,没想到新版本上线以后,数组越界的问题依旧存在。
那到底是什么原因导致的呢?在多次尝试之后和打印。
NSArray *arr1 = @[];
NSArray *arr2 = @[@"1",@"2"];
NSArray *arr3 = @[@"3"];
NSLog(@"%@",[arr1 class]);
NSLog(@"%@",[arr2 class]);
NSLog(@"%@",[arr3 class]);
NSMutableArray *arr4 = [NSMutableArray arrayWithArray:@[]];
NSMutableArray *arr5 = [NSMutableArray arrayWithArray:@[@"1"]];
NSMutableArray *arr6 = [NSMutableArray arrayWithObjects:@[@"2",@"3"], nil];
NSLog(@"%@",[arr4 class]);
NSLog(@"%@",[arr5 class]);
NSLog(@"%@",[arr6 class]);
2018-08-18 15:40:26.667455+0800 Kivie[22422:6746775] __NSArray0
2018-08-18 15:40:26.667619+0800 Kivie[22422:6746775] __NSArrayI
2018-08-18 15:40:26.667961+0800 Kivie[22422:6746775] __NSSingleObjectArrayI
2018-08-18 15:40:26.668216+0800 Kivie[22422:6746775] __NSArrayM
2018-08-18 15:40:26.668237+0800 Kivie[22422:6746775] __NSArrayM
2018-08-18 15:40:26.668250+0800 Kivie[22422:6746775] __NSArrayM
我发现原来NSArray在为空时它的类簇为__NSArray0,而当里面有一个元素时它的类簇为__NSSingleObjectArrayI,而其他情况则为__NSArrayI,而我之前使用[[NSArray array] class] 其实只对__NSArray0,即空数组进行了处理。
另外经过打印我发现当我们使用arr[7]这种取值方式对数组进行取值时,并没有调用objectAtIndex:方法,而是调用了objectAtIndexedSubscript:方法。
找到了问题之后,我又对代码进行了改进如下。
#import "NSObject+SafeCategory.h"
#import <JRSwizzle/JRSwizzle.h>
#define LOG_ERROR(err) \
do { \
if (err) { \
NSLog(@"Error: %@", err); \
err = nil; \
} \
} while(0)
@interface NSArray (SafeCategory)
- (id)objectAtSafeIndex:(NSUInteger)index;
- (id)objectAtSafeIndexSubscript:(NSUInteger)index;
@end
@implementation NSArray (SafeCategory)
- (id)objectAtSafeIndex:(NSUInteger)index
{
if (index > self.count - 1 || !self.count) {
@try {
return [self objectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self objectAtSafeIndex:index];
}
}
- (id)objectAtSafeIndexSubscript:(NSUInteger)index{
if (index > self.count - 1 || !self.count) {
@try {
return [self objectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self objectAtSafeIndexSubscript:index];
}
}
@end
@interface NSMutableArray (SafeCategory)
- (void)TKSafe_addObject:(id)anObject;
- (id)mutableObjectAtSafeIndex:(NSUInteger)index;
- (id)mutableObjectAtSafeIndexSubscript:(NSUInteger)index;
@end
@implementation NSMutableArray (SafeCategory)
- (void)TKSafe_addObject:(id)anObject
{
if (anObject != nil) {
[self TKSafe_addObject:anObject];
} else {
#ifdef DEBUG
NSAssert(NO, @"object is nil");
#endif
}
}
- (id)mutableObjectAtSafeIndex:(NSUInteger)index
{
if (index > self.count - 1 || !self.count) {
@try {
return [self mutableObjectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self mutableObjectAtSafeIndex:index];
}
}
- (id)mutableObjectAtSafeIndexSubscript:(NSUInteger)index{
if (index > self.count - 1 || !self.count) {
@try {
return [self mutableObjectAtSafeIndexSubscript:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self mutableObjectAtSafeIndexSubscript:index];
}
}
@end
@implementation NSObject (SafeCategory)
+ (void)configureSafeMethods
{
NSError *error = nil;
//对不可变数组方法 objectAtIndex: 的三种情况分别进行处理
[objc_getClass("__NSSingleObjectArrayI") jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(objectAtSafeIndex:)
error:&error];
[objc_getClass("__NSArrayI") jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(objectAtSafeIndex:)
error:&error];
[objc_getClass("__NSArray0") jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(objectAtSafeIndex:)
error:&error];
//对不可变数组方法 objectAtIndexedSubscript: 的三种情况分别进行处理
[objc_getClass("__NSSingleObjectArrayI") jr_swizzleMethod:@selector(objectAtIndexedSubscript:)
withMethod:@selector(objectAtSafeIndexSubscript:)
error:&error];
[objc_getClass("__NSArrayI") jr_swizzleMethod:@selector(objectAtIndexedSubscript:)
withMethod:@selector(objectAtSafeIndexSubscript:)
error:&error];
[objc_getClass("__NSArray0") jr_swizzleMethod:@selector(objectAtIndexedSubscript:)
withMethod:@selector(objectAtSafeIndexSubscript:)
error:&error];
LOG_ERROR(error);
//对可变数组方法 objectAtIndex: 的情况分别进行处理
[[[NSMutableArray array] class] jr_swizzleMethod:@selector(objectAtIndex:)
withMethod:@selector(mutableObjectAtSafeIndex:)
error:&error];
//对可变数组方法 objectAtIndexedSubscript: 的情况分别进行处理
[[[NSMutableArray array] class] jr_swizzleMethod:@selector(objectAtIndexedSubscript:)
withMethod:@selector(mutableObjectAtSafeIndexSubscript:)
error:&error];
LOG_ERROR(error);
[[[NSMutableArray array] class] jr_swizzleMethod:@selector(addObject:)
withMethod:@selector(TKSafe_addObject:)
error:&error];
LOG_ERROR(error);
}
这样数组越界导致线上崩溃的问题应该被彻底解决了。