iOS--再也不用擔心陣列越界
最近在網易雲捕上看到一些陣列越界導致的崩潰日誌,所以決定陣列的越界做一些處理。
崩潰報錯資訊
在專案的開發中,筆者一般遇到的問題就是,陣列越界:
-[__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]這樣的取值到底呼叫了何種方法:
通過報錯的函式,我們可以發現,陣列索引呼叫的是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