原文 : 與佳期的個人博客(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
后記
小白出手,請多指教富蓄。如言有誤剩燥,還望斧正!
轉(zhuǎn)載請保留原文地址:http://gonghonglou.com/2019/07/07/crash-guard-container/