作者:橘子star
連結:https://www.jianshu.com/p/1f5c3d43b587
最近在網易雲捕上看到一些陣列越界導致的崩潰日誌,所以決定陣列的越界做一些處理。
崩潰報錯資訊
在專案的開發中,筆者一般遇到的問題就是,陣列越界:
-[__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和文章中有理解不到位或者不足的地方,請大家多多指教,蟹蟹,也祝大家新年快樂!!
●編號300,輸入編號直達本文
●輸入m獲取文章目錄
Web開發
更多推薦《18個技術類微信公眾號》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。