Crash 防護(hù)方案(三):Container (NSArray灾螃、NSDictionary题翻、NSNumber etc.)

原文 : 與佳期的個人博客(gonghonglou.com)

數(shù)組越界這類的 Crash 是最簡單的也是最容易出現(xiàn),業(yè)務(wù)開發(fā)過程中很可能操作某個 NSArray 類型的對象時忘記判空或者忘記長度判斷而造成數(shù)組越界崩潰腰鬼。所以最好是在線上環(huán)境接入這類的 Crash 防護(hù)嵌赠。當(dāng)然,在開發(fā)環(huán)境下最好不要接入熄赡,避免縱容開發(fā)者出現(xiàn)這類遺忘判斷的錯誤姜挺。

這類崩潰的防護(hù)方案無非就是 Hook 可能產(chǎn)生 Crash 的類的相關(guān)方法。之前有過一篇文章是講這類防護(hù)的:從 SafeKit 看異常保護(hù)及 Method Swizzling 使用分析
SafeKit 并未 Hook 全可能出現(xiàn) Crash 的類及其方法彼硫,尤其是 NSArray 類簇炊豪。

關(guān)于類簇這里是蘋果官網(wǎng)文檔:Class Clusters
以及 sunnyxx 在 從NSArray看類簇 文章里的說法:

Class Clusters(類簇)是抽象工廠模式在 iOS 下的一種實(shí)現(xiàn),眾多常用類乌助,如 NSString溜在,NSArray,NSDictionary他托,NSNumber 都運(yùn)作在這一模式下掖肋,它是接口簡單性和擴(kuò)展性的權(quán)衡體現(xiàn),在我們完全不知情的情況下赏参,偷偷隱藏了很多具體的實(shí)現(xiàn)類志笼,只暴露出簡單的接口。

我們來仔細(xì)打印下看看:

// NSArray
NSLog(@"arr alloc:%@", [NSArray alloc].class); // __NSPlaceholderArray
NSLog(@"arr init:%@", [[NSArray alloc] init].class); // __NSArray0

NSLog(@"arr:%@", [@[] class]); // __NSArray0
NSLog(@"arr:%@", [@[@1] class]); // __NSSingleObjectArrayI
NSLog(@"arr:%@", [@[@1, @2] class]); // __NSArrayI
    
// NSMutableArray
NSLog(@"mutA alloc:%@", [NSMutableArray alloc].class); // __NSPlaceholderArray
NSLog(@"mutA init:%@", [[NSMutableArray alloc] init].class); // __NSArrayM

NSLog(@"mutA:%@", [@[].mutableCopy class]); // __NSArrayM
NSLog(@"mutA:%@", [@[@1].mutableCopy class]); // __NSArrayM
NSLog(@"mutA:%@", [@[@1, @2].mutableCopy class]); // __NSArrayM

// NSDictionary
NSLog(@"dict alloc:%@", [NSDictionary alloc].class); // __NSPlaceholderDictionary
NSLog(@"dict init:%@", [[NSDictionary alloc] init].class); // __NSDictionary0

NSLog(@"dict:%@", [@{} class]); // __NSDictionary0
NSLog(@"dict:%@", [@{@1:@1} class]); // __NSSingleEntryDictionaryI
NSLog(@"dict:%@", [@{@1:@1, @2:@2} class]); // __NSDictionaryI

// NSMutableDictionary
NSLog(@"mutD alloc:%@", [NSMutableDictionary alloc].class); // __NSPlaceholderDictionary
NSLog(@"mutD init:%@", [[NSMutableDictionary alloc] init].class); // __NSDictionaryM

NSLog(@"mutD:%@", [@{}.mutableCopy class]); // __NSDictionaryM
NSLog(@"mutD:%@", [@{@1:@1}.mutableCopy class]); // __NSDictionaryM
NSLog(@"mutD:%@", [@{@1:@1, @2:@2}.mutableCopy class]); // __NSDictionaryM

// NSString
NSLog(@"str:%@", [@"" class]); // __NSCFConstantString

// NSNumber
NSLog(@"num:%@", [@1 class]); // __NSCFNumber

以 NSArray 為例把篓,他在 alloc 階段生成的是 __NSPlaceholderArray 的中間對象纫溃,然后在 init 階段給這個中間對象發(fā)消息,由它做工廠韧掩,生成真正的對象紊浩。其中 NSMutableArray 生成的都是 __NSArrayM 類型,M 代表的就是 Mutable。NSArray 則區(qū)分了數(shù)組里:包含 0 個對象時生成的是 __NSArray0 類型坊谁,包含 1 個對象生成的是 __NSSingleObjectArrayI 類型费彼,包含多個對象時生成的是 __NSArrayI 類型。

NSDictionary 同樣是類似的口芍。那我們的防護(hù)方案里則是 Hook 全這些類型箍铲,比如 NSArray 的 Category:

+ (void)load {
    
    // [NSArray alloc]
    [NSClassFromString(@"__NSPlaceholderArray") jr_swizzleMethod:@selector(initWithObjects:count:) withMethod:@selector(initWithObjects_guard:count:) error:nil];
    // @[]
    [NSClassFromString(@"__NSArray0") jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(guard_objectAtIndex:) error:nil];
    [NSClassFromString(@"__NSArray0") jr_swizzleMethod:@selector(arrayByAddingObject:) withMethod:@selector(guard_arrayByAddingObject:) error:nil];
    // @[@1]
    [NSClassFromString(@"__NSSingleObjectArrayI") jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(guard_objectAtIndex:) error:nil];
    [NSClassFromString(@"__NSSingleObjectArrayI") jr_swizzleMethod:@selector(arrayByAddingObject:) withMethod:@selector(guard_arrayByAddingObject:) error:nil];
    // @[@1, @2]
    [NSClassFromString(@"__NSArrayI") jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(guard_objectAtIndex:) error:nil];
    [NSClassFromString(@"__NSArrayI") jr_swizzleMethod:@selector(arrayByAddingObject:) withMethod:@selector(guard_arrayByAddingObject:) error:nil];
}

- (instancetype)initWithObjects_guard:(id *)objects count:(NSUInteger)cnt {
    NSUInteger newCnt = 0;
    for (NSUInteger i = 0; i < cnt; i++) {
        if (!objects[i]) {
            break;
        }
        newCnt++;
    }
    self = [self initWithObjects_guard:objects count:newCnt];
    return self;
}

- (id)guard_objectAtIndex:(NSUInteger)index {
    if (index >= [self count]) {
        // 收集堆棧,上報 Crash
        return nil;
    }
    return [self guard_objectAtIndex:index];
}

- (NSArray *)guard_arrayByAddingObject:(id)anObject {
    if (!anObject) {
        // 收集堆棧鬓椭,上報 Crash
        return self;
    }
    return [self guard_arrayByAddingObject:anObject];
}

當(dāng)然 NSArray颠猴、NSMutableArray、NSDictionary小染、NSMutableDictionary翘瓮、NSString、NSMutableString氧映、NSNumber 這些類都提供了跟多的方法春畔,只要細(xì)心仔細(xì)的將他們?nèi)?Hook 掉就好了。當(dāng)然實(shí)際開發(fā)中可能常用的就那么幾個方法岛都,Hook 那些就已經(jīng)足夠了律姨。

線上接入了這類的防護(hù)之后要比前邊的文章講的 Unrecognized Selector Crash 和 EXC_BAD_ACCESS Crash 更容易造成業(yè)務(wù)邏輯的錯亂,畢竟業(yè)務(wù)邏輯中不可避免的要用到大量的 NSArray臼疫、NSDictionary 類择份,可能在接入這類防護(hù)后會操成點(diǎn)擊無響應(yīng)或者頁面卡死,有時候這種情況甚至比程序崩潰還讓用戶崩潰烫堤,所以也要看實(shí)際開發(fā)需要的取舍荣赶。在接入防護(hù)后尤其要做好堆棧收集,上報 Crash 的工作鸽斟,及時解決掉問題拔创。

Demo 地址:GHLCrashGuard:GHLCrashGuard/Classes/Container

后記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末立倍,一起剝皮案震驚了整個濱河市灭红,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌口注,老刑警劉巖变擒,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寝志,居然都是意外死亡娇斑,警方通過查閱死者的電腦和手機(jī)策添,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毫缆,“玉大人舰攒,你說我怎么就攤上這事』诖祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵兽叮,是天一觀的道長芬骄。 經(jīng)常有香客問我,道長鹦聪,這世上最難降的妖魔是什么账阻? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮泽本,結(jié)果婚禮上淘太,老公的妹妹穿的比我還像新娘。我一直安慰自己规丽,他們只是感情好蒲牧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赌莺,像睡著了一般冰抢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艘狭,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天挎扰,我揣著相機(jī)與錄音,去河邊找鬼巢音。 笑死遵倦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的官撼。 我是一名探鬼主播梧躺,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼歧寺!你這毒婦竟也來了燥狰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤斜筐,失蹤者是張志新(化名)和其女友劉穎龙致,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顷链,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡目代,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榛了。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡在讶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霜大,到底是詐尸還是另有隱情构哺,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布战坤,位于F島的核電站曙强,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏途茫。R本人自食惡果不足惜碟嘴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囊卜。 院中可真熱鬧娜扇,春花似錦、人聲如沸栅组。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玉掸。三九已至致燥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間排截,已是汗流浹背嫌蚤。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留断傲,地道東北人脱吱。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像认罩,于是被迫代替她去往敵國和親箱蝠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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