iOS 數組異常操作的解決辦法

問題的產生

NSString *string = nil;
// 不可變數組
NSArray *array = @[string]; // 初始化中有nil對象
// 可變數組
NSMutableArray *array2 = [NSMutableArray array];
[array2 addObject:string];  // 添加nil對象
// 不可變字典
NSDictionary *dic = @{@"key":string};
// 可變字典
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:string forKey:@"key"];   // 設置nil對象

上述的幾個例子中,都是對數組、字典的異常操作,因為元素中都出現了nil對象,雖然我們可以在添加之前加判斷去除為nil的情況,但是如果內容很多,勢必會很繁瑣,如果有更好的辦法幫我們做完這些繁瑣的事情豈不是美事?

項目中的問題

項目中可能有很多類似下面的寫法

NSArray *array = @[string]; // 初始化中有nil對象
NSDictionary *dic = @{@"key":string};

有的添加了三目運算符,去掉了元素為nil的情況,但是非常的麻煩,有時候甚至會忘記,這就埋下了很多隱患。

解決方案

  • 繼承

如果項目中有父類的存在,我們可以在父類中做些文章,我們可以一些新增數據操作方法,用來過濾掉一些異常操作(比如跳過nil對象部分)

  • 分類

方案一顯然是不理想的,因為項目中可能存在多種父類,情況多變復雜,顯然操作性太低

采用分類方式,分別新增NSArray,NSDictionary等分類文件,為其新增操作方法,在方法中過濾掉異常操作

  • 運行時

方案二較方案一有了更高的操作性,可行性,一定程度上解決了異常操作問題,但是依舊存在著不少問題,例如,
我們添加分類后,我們以后就必須使用新增的方法來操作數據,對于之前的舊代碼依舊未能作出響應,假如全部替換的話,勢必會產生不小的工作量,這不是我們想看到的;
另外,@[],@{}這種方式將不再可用,不,系統的部分操作方法都不可用,局限性還是很大的

那么,有沒有更為優雅的方式解決上述問題呢?答案是有的,就是使用我們OC強大的運行時

基本思路:

1、使用分類
2、在 + (void)load;方法中進行方法交換
3、在自己的方法中處理掉異常

具體實現

例子1:addObject方法添加nil對象

我們先寫一個異常操作

NSString *string = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:string];

錯誤提示

錯誤提示

我們可以看到,__NSArrayM 對象調用了 -insertObject:atIndex: 產生了object cannot be nil的錯誤,顯然易見,addObject方法最終會調用 -insertObject:atIndex: 方法,而對象不能為nil 。

接下來我們來使用運行時交換方法,處理掉這種情況

@implementation NSMutableArray (safe)
+(void)load{
    [self swizze];  
}
+(void)swizze{
    Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:));
    Method new = class_getInstanceMethod(self, @selector(insertObject_safe:atIndex:));
    if (!old || !new) {
        return;
    }
    method_exchangeImplementations(old, new); // 交換方法
}

-(void)insertObject_safe:(id)anObject atIndex:(NSUInteger)index{
    if (index > self.count || !anObject) {
        return; // 過濾到異常部分
    }
    [self insertObject_safe:anObject atIndex:index];
}
@end

將該分類導入需要的文件中,array添加對象時就不會在出現crash問題了。

例子2:數組越界

我們使用不可變數組做例子

NSString *string = nil;
NSArray *array = @[@"0",@"1",@"2"];
NSLog(@"%@",array[5]);

報錯情況

報錯

對象__NSArrayI調用objectAtIndex:出現了越界。

同樣的

@implementation NSArray (safe)
+(void)load{
    [self swizze];
}
+(void)swizze{
    Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
    Method new = class_getInstanceMethod(self, @selector(objectAtIndex_safe:));
    if (!old || !new) {
        return;
    }
    method_exchangeImplementations(old, new);   // 交換方法
}
-(id)objectAtIndex_safe:(NSUInteger)index{
    if (index>=self.count) {
        return nil; // 處理異常部分
    }
    return [self objectAtIndex_safe:index];
}
@end

運行,輸出

輸出

我們看到,當數組越界時,僅僅是返回了 nil。

除了上述兩個例子,系統中還有很多異常操作,比如數組的插入,替換,字典的setObject、字符串的操作、NSRange等等,都是待處理的部分。

總結

相比于在分類中新增方法,使用運行時捕獲對應方法,會更優雅,我們不必再需要大張旗鼓的使用新方法替換舊項目中的系統方法,一勞永逸


優化部分

因為我們過濾了異常部分,無法定位錯誤,我們調試起來異常困難,為此,這種過濾方式最好僅僅在release模式下產生作用,而debug模式下依舊需要crash,這點可以使用宏來控制,也可以使用NSAssert斷言來控制


寫在最后

使用cocoaPods導入相關框架

pod 'SafeKit'
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,172評論 6 13
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,212評論 30 472
  • 今天把 react-native 和 react的 版本都升級到了 各自的 最新版本,然后就遇到了 這個坑, 很多...
    蝸殼美如畫閱讀 967評論 2 2
  • 已經連續四天沒換衣服了 因為冷或是因為懶 近期在看Quora 今天上面有人邀請我回答 中午吃的重慶小面 肉好少 只...
    徐小秋秋秋秋秋閱讀 182評論 0 0
  • 文/黃小妞 01 說到夢想,我小時候的夢想是當老師,那時候覺得老師是最讓人羨慕的事情,勵志長大后要當一名老師,有很...
    黃小妞兒閱讀 305評論 2 5