iOS-數(shù)組防崩潰(全)

書接上回,我們前兩天研究了字典(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];
    }
}

我的注釋Demo:https://github.com/caiqingchong/ArrNilTest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在開發(fā)中我們經(jīng)常遇到數(shù)組越界造成程序crash,我們可以選擇在使用數(shù)組時(shí)添加判斷,但是這種方法比較麻煩,因?yàn)槟沩?xiàng)目...
    Erica0708閱讀 767評(píng)論 0 0
  • 集合類型: Swift,和其他現(xiàn)代編程語言一樣,有內(nèi)置的集合類型數(shù)組(Array)和字典(Dictionary),...
    小松樹先生閱讀 1,161評(píng)論 0 2
  • *7月8日上午 N:Block :跟一個(gè)函數(shù)塊差不多,會(huì)對(duì)里面所有的內(nèi)容的引用計(jì)數(shù)+1,想要解決就用__block...
    炙冰閱讀 2,733評(píng)論 1 14
  • 設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型的...
    Jt_Self閱讀 836評(píng)論 0 4
  • 黑夜是孤獨(dú)的人刷上去的油漆 一層一層都是睡不醒的過去 我聽見夢(mèng)醒時(shí)分的爭吵 沒有一條通向你的內(nèi)心 下一個(gè)黎明之前 ...
    熙寧有安閱讀 511評(píng)論 0 0

友情鏈接更多精彩內(nèi)容