歡迎光臨
每天分享高質量文章

iOS–再也不用擔心陣列越界

作者:橘子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], systemSelectormethod_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {

        class_replaceMethod([self class], swizzledSelectormethod_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、資料庫、運維等。

贊(0)

分享創造快樂