iOS 避免常見崩潰(一)

級別: ★★☆☆☆
標(biāo)簽:「iOS 」「避免常見崩潰」
作者: WYW
審校: QiShare團(tuán)隊(duì)

筆者最近看了部分引起App Crash的常見情況疯潭,這次先討論下操作集合類型(如NSArray郁稍,NSDictionary等)時(shí)门躯,防止常見崩潰(如避免從數(shù)組中取值時(shí)越界、往字典中插入為nil的value等)的內(nèi)容殉摔。

  • 為了避免崩潰凄贩,操作集合類對象時(shí)淑玫,設(shè)置值和取值的時(shí)候崎岂,可以考慮使用如下方法:
      1. 使用分類添加的安全性方法
      1. 使用交換系統(tǒng)方法和我們做了安全性處理的方法

安全操作集合類對象:添加分類方法

當(dāng)操作集合類對象的時(shí)候捆毫,可以使用我們添加的安全取值的分類。

添加分類方法部分冲甘,先從NSString* 和NSNumber* 的integerValue談起冻璃。

  • (1)如在NSObject的分類中添加的qi_safeIntegerValue用于替換平時(shí)用的integerValue的方法。
- (NSInteger)qi_safeIntegerValue {
    
    if ([self isKindOfClass:[NSNumber class]]) {
        return [((NSNumber *)self) integerValue];
    } else if([self isKindOfClass:[NSString class]]) {
        return [((NSString *)self) integerValue];
    } else {
        return kCustomErrorCode;
    }
}

使用qi_safeIntegerValue的方式為:

    id number = @(1);
    [number qi_safeIntegerValue];
  • (2)如在NSObject的分類中添加了qi_safeArrayObjectAtIndex用于替換NSArray* 的- (ObjectType)objectAtIndex:(NSUInteger)index;
- (id)qi_safeArrayObjectAtIndex:(NSUInteger)index {
    
    if (![self isKindOfClass:[NSArray class]]) {
        return nil;
    }
    
    if (index < 0 || index >= ((NSArray *)self).count) {
        return nil;
    }
    
    return [(NSArray *)self objectAtIndex:index];
}

使用qi_safeArrayObjectAtIndex的方式為:

    NSArray *qiArr = @[@1];
    [qiArr qi_safeArrayObjectAtIndex:0];
    [qiArr qi_safeArrayObjectAtIndex:1];
  • 更多相關(guān)內(nèi)容损合,可查看Demo QiSafeType

不過只使用分類娘纷,不適于我們使用字面量語法取值的情況嫁审。如對于數(shù)組來說,如果我們想要使用qiArr[0]這樣的字面量語法取值赖晶,使用當(dāng)前的分類的方式就不適用了律适。此時(shí)就需要結(jié)合runtime使用交換qiArr[0]調(diào)用的系統(tǒng)方法和我們自己添加的安全取值的方法來達(dá)到字面量安全取值的目的。

安全操作集合類對象:方法交換

安全操作集合類對象遏插,方法交換部分捂贿,筆者會以字面量操作NSArray聊聊相關(guān)內(nèi)容。

以字面量的方式聲明數(shù)組 及取值:

// 聲明數(shù)組的時(shí)候 插入nil
NSString *nilValue = nil;
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];

NSString *nilValue = nil;
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
NSLog(@"qiArr:%@", qiArr);

// 從數(shù)組中取值 故意越界取值
NSLog(@"qiArr[0]:%@ qiArr[1]:%@ qiArr[2]:%@",qiArr[0],qiArr[1],qiArr[2]);

// 輸出結(jié)果如下:
qiArr:(
    qishare0,
    qiShare2
)

qiArr[0]:qishare0 qiArr[1]:qiShare2 qiArr[2]:(null)
  • 交換方法的操作需要引入#import <objc/runtime.h>胳嘲,主要會用到下邊的API厂僧。
// Returns a pointer to the data structure describing a given class method for a given class.
// 返回一個(gè)描述指定類cls和給定類方法的數(shù)據(jù)結(jié)構(gòu)的指針 其實(shí)返回值也是一個(gè)Method
// 獲取指定類Cls和指定方法sel的對應(yīng)的 類 Method
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

// Returns a specified instance method for a given class.
// 返回指定的類cls及相應(yīng)selector的實(shí)例方法
// 獲取指定類Cls和指定方法sel的對應(yīng)的 實(shí)例 Method
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
   
// 交換Method m1和Method m2的實(shí)現(xiàn) 
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • 交換方法需要待交換方法SEL,及待交換的類Class

以下內(nèi)容筆者會以字面量的方式聲明qiArr NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];為例進(jìn)行分析了牛,所需的class及要交換的SEL

  • (1)待交換的方法

就待交換的方法而言颜屠,聲明qiArr的時(shí)候。首先要確定調(diào)用的NSArray哪個(gè)方法鹰祸。調(diào)用的是+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;甫窟;這個(gè)可以通過自己寫一個(gè)數(shù)組的子類來證實(shí)這件事,也可以通過交換方法后調(diào)用的方法來反面驗(yàn)證蛙婴。NSArray的子類的寫法可以參考QiSafeType中的QiSubArray的寫法粗井。

關(guān)于繼承NSArray:Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.

+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;是一個(gè)類方法,所以方法交換部分,筆者也寫了一個(gè)安全聲明數(shù)組的類方法浇衬。+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt

筆者寫的待交換的+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt的方法體如下:

+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt {
    
    id instance = nil;
    id safeObjs[cnt];
    NSUInteger j = 0;
    for (NSUInteger i = 0; i < cnt; i ++) {
        if (!objects[i]) {
            continue;
        }
        safeObjs[j++] = objects[i];
    }
    instance = [self qisafeArrayWithObjects:safeObjs count:j];
    return instance;
}

思想:在聲明字面量數(shù)組的時(shí)候懒构,先遍歷指定的數(shù)組,過濾掉為空的對象径玖,剩余的存放到另一個(gè)數(shù)組中痴脾。再調(diào)用自己添加的聲明數(shù)組的類方法(因?yàn)槲覀兲砑拥姆椒ê拖到y(tǒng)的方法進(jìn)行了方法交換,所以這里實(shí)質(zhì)是調(diào)用的系統(tǒng)的聲明數(shù)組的方法梳星。)

  • (2)待交換的類

聲明qiArr的時(shí)候待交換的方法為類方法赞赖,所以待交換的類為[NSArray class]

就待交換的方法而言。以qiArr[0]為例冤灾。要確定調(diào)用的數(shù)組的那個(gè)方法前域。調(diào)用的是- (ObjectType)objectAtIndex:(NSUInteger)index;這個(gè)可以通過自己寫一個(gè)數(shù)組的子類來證實(shí)這件事,也可以通過交換方法后調(diào)用的方法來反面驗(yàn)證韵吨。

上述問題明確了之后匿垄,我們就可以在NSArray的分類的+ (void)load方法中根據(jù)指定的類[NSArray class]把+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt進(jìn)行方法交換了。

關(guān)鍵代碼如下:

    Method originMethod = class_getClassMethod([NSArray class], @selector(arrayWithObjects:count:));
    Method alterMethod = class_getClassMethod([NSArray class], @selector(qisafeArrayWithObjects:count:));
    method_exchangeImplementations(originMethod, alterMethod);

以上部分归粉,筆者解釋了椿疗,NSArray以字面量的方式聲明數(shù)組的方法交換部分的內(nèi)容,下邊筆者將繼續(xù)和大家聊一下關(guān)于NSArray字面量取值的內(nèi)容糠悼。其實(shí)NSArray運(yùn)作在抽象工廠設(shè)計(jì)模式下届榄。抽象工廠模式是類簇在iOS下的的一種實(shí)現(xiàn)。眾多常用類倔喂,如NSString铝条,NSArray,NSDictionary席噩,NSNumber都運(yùn)作在抽象工廠模式下班缰,引自從NSArray看類簇
以下部分,大家也可以直接看Demo中的寫法自行分析悼枢。

筆者想通過以下代碼說明埠忘,創(chuàng)建出的NSArray*的實(shí)例(qiArr)的class,和創(chuàng)建qiArr的方式及qiArr中包含的對象的個(gè)數(shù)有關(guān)馒索。

NSString *nilValue = nil;
// 聲明數(shù)組的時(shí)候 插入nil
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
NSLog(@"qiArr:%@", qiArr);
NSLog(@"qiArr[0]:%@ qiArr[1]:%@ qiArr[2]:%@",qiArr[0],qiArr[1],qiArr[2]);

id tempValue = nil;
    
NSArray *qiArr0 = @[];
tempValue = qiArr0[0];
tempValue = qiArr0[1];
    
NSArray *qiArr1 = @[@1];
tempValue = qiArr1[0];
tempValue = qiArr1[1];
    
NSArray *qiArr2 = @[@1, @2];
tempValue = qiArr2[1];
tempValue = qiArr2[2];
    
NSLog(@"qiArr0 class:%@", NSStringFromClass([qiArr0 class]));
NSLog(@"qiArr1 class:%@", NSStringFromClass([qiArr1 class]));
NSLog(@"qiArr2 class:%@", NSStringFromClass([qiArr2 class]));
    
NSArray *qiArr3 = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
    
NSLog(@"qiArr3 class:%@", NSStringFromClass([qiArr3 class]));

 qiArr0 class:__NSArray0
 qiArr1 class:__NSSingleObjectArrayI
 qiArr2 class:__NSArrayI
 qiArr3 class:__NSArrayI

有了上述內(nèi)容给梅,我們就可以在通過字面量語法故意越界訪問數(shù)組對象的時(shí)候,根據(jù)崩潰的提示双揪,添加上相應(yīng)的方法交換动羽。

關(guān)鍵代碼如下:

    // __NSArray0
    Method originArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(objectAtIndex:));
    Method alterArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(qiSafeArr0ObjectAtIndex:));
    method_exchangeImplementations(originArr0ObjectAtIndexMethod, alterArr0ObjectAtIndexMethod);

   // __NSSingleObjectArrayI
    Method originSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(objectAtIndex:));
    Method alterSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(qiSafeSingleObjArrIObjectAtIndex:));
    method_exchangeImplementations(originSingleObjArrIObjectAtIndexMethod, alterSingleObjArrIObjectAtIndexMethod);
    
    // 注意__NSArrayI調(diào)用字面量語法的時(shí)候調(diào)用的系統(tǒng)方法為`- (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));` 和上述類型的NSArray待交換的方法不同,這一點(diǎn)可以故意測試崩潰渔期,查看崩潰的提示得出运吓。
    
    // __NSArrayI
    Method originArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndexedSubscript:));
    Method alterArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(qiSafeArrIObjAtIndexedSubscript:));
    method_exchangeImplementations(originArrIObjAtIndexedSubMethod, alterArrIObjAtIndexedSubMethod);

Demo

  • 更多相關(guān)內(nèi)容渴邦,可查看Demo QiSafeType

參考學(xué)習(xí)網(wǎng)址


推薦文章:
iOS消息轉(zhuǎn)發(fā)
iOS 自定義拖拽式控件:QiDragView
iOS 自定義卡片式控件:QiCardView
iOS Wireshark抓包
iOS Charles抓包
初探TCP
IP拘哨、UDP初探

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谋梭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子倦青,更是在濱河造成了極大的恐慌瓮床,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件产镐,死亡現(xiàn)場離奇詭異隘庄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)癣亚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門丑掺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人述雾,你說我怎么就攤上這事街州。” “怎么了玻孟?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵摔癣,是天一觀的道長蛹锰。 經(jīng)常有香客問我狼讨,道長檐蚜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任玩敏,我火速辦了婚禮,結(jié)果婚禮上质礼,老公的妹妹穿的比我還像新娘旺聚。我一直安慰自己,他們只是感情好眶蕉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布砰粹。 她就那樣靜靜地躺著,像睡著了一般造挽。 火紅的嫁衣襯著肌膚如雪碱璃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天饭入,我揣著相機(jī)與錄音嵌器,去河邊找鬼。 笑死谐丢,一個(gè)胖子當(dāng)著我的面吹牛爽航,可吹牛的內(nèi)容都是我干的蚓让。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼讥珍,長吁一口氣:“原來是場噩夢啊……” “哼历极!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衷佃,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤趟卸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后氏义,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锄列,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年觅赊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了右蕊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吮螺,死狀恐怖饶囚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸠补,我是刑警寧澤萝风,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站紫岩,受9級特大地震影響规惰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泉蝌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一歇万、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勋陪,春花似錦贪磺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至违孝,卻和暖如春刹前,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雌桑。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工喇喉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人校坑。 一個(gè)月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓轧飞,卻偏偏與公主長得像衅鹿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子过咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式是什么大渤? 你知道哪些設(shè)計(jì)模式,并簡要敘述掸绞? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn)泵三,就是用比較成熟的邏輯去處理某一種類型的...
    Jt_Self閱讀 747評論 0 4
  • NSArray全部API學(xué)習(xí)。 返回?cái)?shù)組指定下標(biāo)的元素 - ()objectAtIndex:(NSUInteger...
    ElvisSun閱讀 789評論 0 2
  • 1. 熟悉Git的基本流程 git clone git add -A git commit -m " " git ...
    9bf19a4010ab閱讀 1,632評論 0 2
  • 你是否有過如下的經(jīng)歷: 1. 選擇的東西種類越多,越不知道該選什么敞映。 2. 買了很多東西较曼,當(dāng)時(shí)很喜歡,可一直就沒用...
    幕影心閱讀 506評論 1 8
  • 怎么辦振愿? 突然有點(diǎn)不想結(jié)婚的念頭捷犹。 人生如此之短, 青春又是多么的寶貴冕末。 為什么要去為了一個(gè)不喜歡的人結(jié)婚萍歉, 然后...
    零九年白T恤閱讀 163評論 0 0