原文 : 與佳期的我的博客(gonghonglou.com)html
數組越界這類的 Crash 是最簡單的也是最容易出現,業務開發過程當中極可能操做某個 NSArray 類型的對象時忘記判空或者忘記長度判斷而形成數組越界崩潰。因此最好是在線上環境接入這類的 Crash 防禦。固然,在開發環境下最好不要接入,避免縱容開發者出現這類遺忘判斷的錯誤。git
這類崩潰的防禦方案無非就是 Hook 可能產生 Crash 的類的相關方法。以前有過一篇文章是講這類防禦的:從 SafeKit 看異常保護及 Method Swizzling 使用分析 但 SafeKit 並未 Hook 全可能出現 Crash 的類及其方法,尤爲是 NSArray 類簇。github
關於類簇這裏是蘋果官網文檔:Class Clusters 以及 sunnyxx 在 從NSArray看類簇 文章裏的說法:數組
Class Clusters(類簇)是抽象工廠模式在 iOS 下的一種實現,衆多經常使用類,如 NSString,NSArray,NSDictionary,NSNumber 都運做在這一模式下,它是接口簡單性和擴展性的權衡體現,在咱們徹底不知情的狀況下,偷偷隱藏了不少具體的實現類,只暴露出簡單的接口。bash
咱們來仔細打印下看看:app
// 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 階段給這個中間對象發消息,由它作工廠,生成真正的對象。其中 NSMutableArray 生成的都是 __NSArrayM
類型,M 表明的就是 Mutable。NSArray 則區分了數組裏:包含 0 個對象時生成的是 __NSArray0
類型,包含 1 個對象生成的是 __NSSingleObjectArrayI
類型,包含多個對象時生成的是 __NSArrayI
類型。ui
NSDictionary 一樣是相似的。那咱們的防禦方案裏則是 Hook 全這些類型,好比 NSArray 的 Category:spa
+ (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];
}
複製代碼
固然 NSArray、NSMutableArray、NSDictionary、NSMutableDictionary、NSString、NSMutableString、NSNumber 這些類都提供了跟多的方法,只要細心仔細的將他們全 Hook 掉就行了。固然實際開發中可能經常使用的就那麼幾個方法,Hook 那些就已經足夠了。code
線上接入了這類的防禦以後要比前邊的文章講的 Unrecognized Selector Crash 和 EXC_BAD_ACCESS Crash 更容易形成業務邏輯的錯亂,畢竟業務邏輯中不可避免的要用到大量的 NSArray、NSDictionary 類,可能在接入這類防禦後會操成點擊無響應或者頁面卡死,有時候這種狀況甚至比程序崩潰還讓用戶崩潰,因此也要看實際開發須要的取捨。在接入防禦後尤爲要作好堆棧收集,上報 Crash 的工做,及時解決掉問題。htm
Demo 地址:GHLCrashGuard:GHLCrashGuard/Classes/Container
小白出手,請多指教。如言有誤,還望斧正!
轉載請保留原文地址:gonghonglou.com/2019/07/07/…