書接上回,我們前兩天研究了字典(Dictionary)崩潰的處理方式以及NSException類,而OC一個(gè)極為重要的類(Array)也進(jìn)入了我們的視線,在開發(fā)過程中,我們遇到的最多的崩潰之一就是數(shù)組越界。針對(duì)這個(gè)問題,今天就讓我們來詳細(xì)分析如何處理數(shù)組越界導(dǎo)致的崩潰吧。
一、不可變數(shù)組的分析(NSArray)
1、首先我們創(chuàng)建NSArray的類別:
#import "NSArray+NilSafe.h"
#import <objc/runtime.h>
#import "NSObject+Swizzling.h"http://在NSString類別中交換方法,通用類
2、在load方法中獲取原方法以及替換方法,利用GCD只執(zhí)行一次,防止多線程問題:
+ (void)load{
static dispatch_once_t onceToken;
//調(diào)用原方法以及新方法進(jìn)行交換,處理崩潰問題。
dispatch_once(&onceToken, ^{
//越界崩潰方式一:[array objectAtIndex:1000];
[objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndex:) withSwizzledSelector:@selector(safeObjectAtIndex:)];
//越界崩潰方式二:arr[1000]; Subscript n:下標(biāo)、腳注
[objc_getClass("__NSArrayI") swizzleSelector:@selector(objectAtIndexedSubscript:) withSwizzledSelector:@selector(safeobjectAtIndexedSubscript:)];
});
}
-
重點(diǎn):這里老鐵們要千萬注意,我們獲取數(shù)組中的數(shù)據(jù)有兩種方法,一種是[array objectAtIndex:1000],另一種是arr[1000],但是千萬不要以為這兩種方式調(diào)用的方法都是一樣的(被坑過/(ㄒoㄒ)/~~),arr[1000]的調(diào)用方法是objectAtIndexedSubscript:,所以也要針對(duì)這個(gè)方法處理。我們可以從崩潰的日志里面看到,如下圖:
arr[x]越界崩潰提示.png
在SDK中的方法如下圖:
對(duì)應(yīng)的方法.png
3、在交換方法中對(duì)越界的索引處理,這里可以返回nil或者根據(jù)你的需求返回一個(gè)你想要的值:
- (instancetype)safeObjectAtIndex:(NSUInteger)index {
// 數(shù)組越界也不會(huì)崩,但是開發(fā)的時(shí)候并不知道數(shù)組越界
if (index > (self.count - 1)) { // 數(shù)組越界
return nil;
}else { // 沒有越界
return [self safeObjectAtIndex:index];
}
}
- (instancetype)safeobjectAtIndexedSubscript:(NSUInteger)index{
if (index > (self.count - 1)) { // 數(shù)組越界
return nil;
}else { // 沒有越界
return [self safeobjectAtIndexedSubscript:index];
}
}
二、可變數(shù)組的分析(NSMutableArray)
1、創(chuàng)建NSMutableArray的分類,并且導(dǎo)入相應(yīng)文件
#import "NSMutableArray+NilSafe.h"
#import <objc/runtime.h>
#import "NSObject+Swizzling.h"
2、在load方法中交換相應(yīng)的方法
由于NSMutableArray相對(duì)于NSArray可以執(zhí)行插入、替換、刪除等操作,數(shù)組越界的情況會(huì)比NSArray更多,所以為了妥善起見,我們針對(duì)各個(gè)方法都要作相應(yīng)的處理。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//1、提示移除的數(shù)據(jù)不能為空
[self swizzleSelector:@selector(removeObject:)
withSwizzledSelector:@selector(hdf_safeRemoveObject:)];
//2、提示數(shù)組不能添加為nil的數(shù)據(jù)
[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
withSwizzledSelector:@selector(hdf_safeAddObject:)];
//3、移除數(shù)據(jù)越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(removeObjectAtIndex:)
withSwizzledSelector:@selector(hdf_safeRemoveObjectAtIndex:)];
//4、插入數(shù)據(jù)越界
[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、替換某個(gè)數(shù)據(jù)越界
[objc_getClass("__NSArrayM") swizzleSelector:@selector(replaceObjectAtIndex:withObject:) withSwizzledSelector:@selector(safereplaceObjectAtIndex:withObject:)];
//8、添加數(shù)據(jù)中有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);
}
}
// 因?yàn)橛兄禐閚il的元素,那么我們可以過濾掉值為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)) { // 數(shù)組越界
NSLog(@"索引越界");
return nil;
}else { // 沒有越界
return [self safeobjectAtIndexedSubscript:index];
}
}
- (instancetype)safereplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject{
if (index > (self.count - 1)) { // 數(shù)組越界
NSLog(@"移除索引越界");
return nil;
}else { // 沒有越界
return [self safeobjectAtIndexedSubscript:index];
}
}

