书接上回,我们前两天研究了字典(Dictionary)崩溃的处理方式以及NSException类,而OC一个极为重要的类(Array)也进入了我们的视线,在开发过程中,我们遇到的最多的崩溃之一就是数组越界。针对这个问题,今天就让我们来详细分析如何处理数组越界导致的崩溃吧。
一、不可变数组的分析(NSArray)
1、首先我们创建NSArray的类别:
#import "NSArray+NilSafe.h"
#import <objc/runtime.h>
#import "NSObject+Swizzling.h"//在NSString类别中交换方法,通用类
2、在load方法中获取原方法以及替换方法,利用GCD只执行一次,防止多线程问题:
+ (void)load{
static dispatch_once_t onceToken;
//调用原方法以及新方法进行交换,处理崩溃问题。
dispatch_once(&onceToken, ^{
//越界崩溃方式一:[array objectAtIndex:1000];
[objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndex:) withSwizzledSelector:@selector(safeObjectAtIndex:)];
//越界崩溃方式二:arr[1000]; Subscript n:下标、脚注
[objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndexedSubscript:) withSwizzledSelector:@selector(safeobjectAtIndexedSubscript:)];
});
}
-
重点:这里老铁们要千万注意,我们获取数组中的数据有两种方法,一种是[array objectAtIndex:1000],另一种是arr[1000],但是千万不要以为这两种方式调用的方法都是一样的(被坑过/(ㄒoㄒ)/~~),arr[1000]的调用方法是objectAtIndexedSubscript:,所以也要针对这个方法处理。我们可以从崩溃的日志里面看到,如下图:
arr[x]越界崩溃提示.png
在SDK中的方法如下图:
对应的方法.png
3、在交换方法中对越界的索引处理,这里可以返回nil或者根据你的需求返回一个你想要的值:
- (instancetype)safeObjectAtIndex:(NSUInteger)index {
// 数组越界也不会崩,但是开发的时候并不知道数组越界
if (index > (self.count - 1)) { // 数组越界
return nil;
}else { // 没有越界
return [self safeObjectAtIndex:index];
}
}
- (instancetype)safeobjectAtIndexedSubscript:(NSUInteger)index{
if (index > (self.count - 1)) { // 数组越界
return nil;
}else { // 没有越界
return [self safeobjectAtIndexedSubscript:index];
}
}
二、可变数组的分析(NSMutableArray)
1、创建NSMutableArray的分类,并且导入相应文件
#import "NSMutableArray+NilSafe.h"
#import <objc/runtime.h>
#import "NSObject+Swizzling.h"
2、在load方法中交换相应的方法
由于NSMutableArray相对于NSArray可以执行插入、替换、删除等操作,数组越界的情况会比NSArray更多,所以为了妥善起见,我们针对各个方法都要作相应的处理。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//1、提示移除的数据不能为空
[self swizzleSelector:@selector(removeObject:)
withSwizzledSelector:@selector(hdf_safeRemoveObject:)];
//2、提示数组不能添加为nil的数据
[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
withSwizzledSelector:@selector(hdf_safeAddObject:)];
//3、移除数据越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(removeObjectAtIndex:)
withSwizzledSelector:@selector(hdf_safeRemoveObjectAtIndex:)];
//4、插入数据越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(insertObject:atIndex:)
withSwizzledSelector:@selector(hdf_insertObject:atIndex:)];
//5、处理[arr objectAtIndex:1000]这样的越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(objectAtIndex:) withSwizzledSelector:@selector(hdf_objectAtIndex:)];
//6、处理arr[1000]这样的越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(objectAtIndexedSubscript:) withSwizzledSelector:@selector(safeobjectAtIndexedSubscript:)];
//7、替换某个数据越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(replaceObjectAtIndex:withObject:) withSwizzledSelector:@selector(safereplaceObjectAtIndex:withObject:)];
//8、添加数据中有nil的情况,剔除掉nil
[objc_getClass("__NSPlaceholderArray") swizzleSelector:@selector(initWithObjects:count:) withSwizzledSelector:@selector(hdf_initWithObjects:count:)];
});
}
3、替换方法的处理
- (instancetype)hdf_initWithObjects:(const id _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt {
BOOL hasNilObject = NO;
for (NSUInteger i = 0; i < cnt; i++) {
if ([objects[i] isKindOfClass:[NSArray class]]) {
NSLog(@"%@", objects[i]);
}
if (objects[i] == nil) {
hasNilObject = YES;
NSLog(@"%s object at index %lu is nil, it will be filtered", __FUNCTION__, i);
}
}
// 因为有值为nil的元素,那么我们可以过滤掉值为nil的元素
if (hasNilObject) {
id __unsafe_unretained newObjects[cnt];
NSUInteger index = 0;
for (NSUInteger i = 0; i < cnt; ++i) {
if (objects[i] != nil) {
newObjects[index++] = objects[i];
}
}
NSLog(@"%@", [NSThread callStackSymbols]);
return [self hdf_initWithObjects:newObjects count:index];
}
return [self hdf_initWithObjects:objects count:cnt];
}
- (void)hdf_safeAddObject:(id)obj {
if (obj == nil) {
NSLog(@"%s can add nil object into NSMutableArray", __FUNCTION__);
} else {
[self hdf_safeAddObject:obj];
}
}
- (void)hdf_safeRemoveObject:(id)obj {
if (obj == nil) {
NSLog(@"%s call -removeObject:, but argument obj is nil", __FUNCTION__);
return;
}
[self hdf_safeRemoveObject:obj];
}
- (void)hdf_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (anObject == nil) {
NSLog(@"%s can't insert nil into NSMutableArray", __FUNCTION__);
} else if (index > self.count) {
NSLog(@"%s index is invalid", __FUNCTION__);
} else {
[self hdf_insertObject:anObject atIndex:index];
}
}
- (id)hdf_objectAtIndex:(NSUInteger)index {
if (self.count == 0) {
NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
return nil;
}
if (index > self.count) {
NSLog(@"%s index out of bounds in array", __FUNCTION__);
return nil;
}
return [self hdf_objectAtIndex:index];
}
- (void)hdf_safeRemoveObjectAtIndex:(NSUInteger)index {
if (self.count <= 0) {
NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
return;
}
if (index >= self.count) {
NSLog(@"%s index out of bound", __FUNCTION__);
return;
}
[self hdf_safeRemoveObjectAtIndex:index];
}
// 1、索引越界 2、移除索引越界 3、替换索引越界
- (instancetype)safeobjectAtIndexedSubscript:(NSUInteger)index{
if (index > (self.count - 1)) { // 数组越界
NSLog(@"索引越界");
return nil;
}else { // 没有越界
return [self safeobjectAtIndexedSubscript:index];
}
}
- (instancetype)safereplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject{
if (index > (self.count - 1)) { // 数组越界
NSLog(@"移除索引越界");
return nil;
}else { // 没有越界
return [self safeobjectAtIndexedSubscript:index];
}
}
最后,感谢黄仪标大神,在我分析中大量参考和使用了他的代码。同时,我也对其中一些有缺陷的地方做了补充和注释(如:补充了arr[1000]这种越界处理)。下面同时奉上黄仪标以及我自己注释过的代码供大家参考,如果本人有理解错误或者表述不清的地方,欢迎大家随时指出来。O(∩_∩)O~~
黄仪标runtimeDemo:https://github.com/CoderJackyHuang/RuntimeDemo
我的注释Demo:https://github.com/caiqingchong/ArrNilTest
网友评论