源碼地址: AvoidCrash
https://github.com/chenfanfang/AvoidCrash
前言
一個已經發布到AppStore上的App,最忌諱的就是崩潰問題。為什么在開發階段或者測試階段都不會崩潰,而發布到AppStore上就崩潰了呢?究其根源,最主要的原因就是數據的錯亂。特別是 服務器返回數據的錯亂,將嚴重影響到我們的App。
Foundation框架存在許多潛在崩潰的危險
- 將 nil 插入可變數組中會導致崩潰。
- 數組越界會導致崩潰。
- 根據key給字典某個元素重新賦值時,若key為 nil 會導致崩潰。
- ......
AvoidCrash簡介
- 這個框架利用runtime技術對一些常用并且容易導致崩潰的方法進行處理,可以有效的防止崩潰。
- 并且打印出具體是哪個方法會導致崩潰,讓你快速定位導致崩潰的代碼。
- 你可以獲取到原本導致崩潰的主要信息<由于這個框架的存在,并不會崩潰>,進行相應的處理。比如:
- 你可以將這些崩潰信息發送到自己服務器。
- 你若集成了第三方崩潰日志收集的SDK,比如你用了騰訊的Bugly,你可以上報自定義異常。
- 或許你會問有JSPatch就可以下發補丁來修復bug,為什么要用AvoidCrash?我只能說,AvoidCrash可以有效防止部分常見崩潰,JSPatch可以快速修復bug.推薦將兩者都集成到項目中去。
下面先來看下防止崩潰的效果吧
可導致崩潰的代碼
NSString *nilStr = nil;
NSArray *array = @[@"chenfanfang", nilStr];
- 若沒有AvoidCrash來防止崩潰,則會直接崩潰,如下圖
- 若有AvoidCrash來防止崩潰,則不會崩潰,并且會將原本會崩潰情況的詳細信息打印出來,如下圖
Installation【安裝】
From CocoaPods【使用CocoaPods】
pod AvoidCrash
Manually【手動導入】
- Drag all source files under floder
AvoidCrash
to your project.【將AvoidCrash
文件夾中的所有源代碼拽入項目中】 - 對 NSMutableArray+AvoidCrash.m 文件進行 -fno-objc-arc 設置(若使用CocoaPods集成則無需手動配置),配置過程如下圖:
使用方法
- 在AppDelegate的didFinishLaunchingWithOptions方法中添加如下代碼,讓AvoidCrash生效
//這句代碼會讓AvoidCrash生效,若沒有如下代碼,則AvoidCrash就不起作用
[AvoidCrash becomeEffective];
/*
* [AvoidCrash becomeEffective],是全局生效。若你只需要部分生效,你可以單個進行處理,比如:
* [NSArray avoidCrashExchangeMethod];
* [NSMutableArray avoidCrashExchangeMethod];
* .................
* .................
*/
- 若你想要獲取崩潰日志的所有詳細信息,只需添加通知的監聽,監聽的通知名為:AvoidCrashNotification
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[AvoidCrash becomeEffective];
//監聽通知:AvoidCrashNotification, 獲取AvoidCrash捕獲的崩潰日志的詳細信息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:AvoidCrashNotification object:nil];
return YES;
}
- (void)dealwithCrashMessage:(NSNotification *)note {
//注意:所有的信息都在userInfo中
//你可以在這里收集相應的崩潰信息進行相應的處理(比如傳到自己服務器)
NSLog(@"%@",note.userInfo);
}
- 下面通過打斷點的形式來看下userInfo中的信息結構,看下包含了哪些信息
- 再看下控制臺輸出日志來看下userInfo中的包含了哪些信息
目前可以防止崩潰的方法有
- NSArray
1. NSArray的快速創建方式 NSArray *array = @[@"chenfanfang", @"AvoidCrash"]; //這種創建方式其實調用的是2中的方法
2. +(instancetype)arrayWithObjects:(const id _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt
-
3. 通過下標獲取元素 array[100]、[array objectAtIndex:100]
- (id)objectAtIndex:(NSUInteger)index
4. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes
5. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range
- NSMutableArray
-
1. 通過下標獲取元素 array[100]、[array objectAtIndex:100]
- (id)objectAtIndex:(NSUInteger)index
2. - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
3. - (void)removeObjectAtIndex:(NSUInteger)index
4. - (void)insertObject:(id)anObject atIndex:(NSUInteger)index
5. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes
6. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range
-
- NSDictionary
1. NSDictionary的快速創建方式 NSDictionary *dict = @{@"frameWork" : @"AvoidCrash"}; //這種創建方式其實調用的是2中的方法
2. +(instancetype)dictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt
- NSMutableDictionary
1. - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
2. - (void)removeObjectForKey:(id)aKey
- NSString
1. - (unichar)characterAtIndex:(NSUInteger)index
2. - (NSString *)substringFromIndex:(NSUInteger)from
3. - (NSString *)substringToIndex:(NSUInteger)to {
4. - (NSString *)substringWithRange:(NSRange)range {
5. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
6. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange
7. - (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
- NSMutableString
1. 由于NSMutableString是繼承于NSString,所以這里和NSString有些同樣的方法就不重復寫了
2. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
3. - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
4. - (void)deleteCharactersInRange:(NSRange)range
- KVC
1.- (void)setValue:(id)value forKey:(NSString *)key
2.- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
3.- (void)setValue:(id)value forUndefinedKey:(NSString *)key //這個方法一般用來重寫,不會主動調用
4.- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues
- NSAttributedString
1.- (instancetype)initWithString:(NSString *)str
2.- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr
3.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs
- NSMutableAttributedString
1.- (instancetype)initWithString:(NSString *)str
2.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs
更新
2016-10-15
- 修復上一個版本部分方法不能攔截崩潰的BUG,具體修復哪些可以查看issues和簡書上的留言。
- 優化崩潰代碼的定位,定位崩潰代碼更加準確。
- 增加對KVC賦值防止崩潰的處理。
- 增加對NSAttributedString防止崩潰的處理
- 增加對NSMutableAttributedString防止崩潰的處理
2016-11-29
- 修復在鍵盤彈出狀態下,按Home鍵進入后臺會導致崩潰的bug。
- 新增防止崩潰(NSArray、NSMutableArray)
- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes
2016-12-1
處理數組的類簇問題,提高兼容性,不論是由于array[100]方式,還是[array objectAtIndex:100]方式 獲取數組中的某個元素操作不當而導致的crash,都能被攔截防止崩潰。
上一個版本只能防止array[100]導致的崩潰,不能防止[array objectAtIndex:100]導致的崩潰。
統一對線程進行處理,監聽通知AvoidCrashNotification后,不論是在主線程導致的crash還是在子線程導致的crash,監聽通知的方法統一在"主線程"中。
上一個版本中,在哪個線程導致的crash, 則監聽通知的方法就在哪個線程中。
新增防止崩潰 (NSArray、NSMutableArray)
- (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range
期待
- 如果你發現了哪些常用的Foundation中的方法存在潛在崩潰的危險,而這個框架中沒有進行處理,希望你能 issue, 或者在簡書私信我,我將這些方法也添加到AvoidCrash中。謝謝
- 如果你在使用過程中遇到BUG,希望你能 issue, 或者在簡書私信我。謝謝(或者嘗試下載最新的框架代碼看看BUG修復沒有)
- 畢竟一個人的能力有限,時間有限,希望有能力的你可以加入到這個項目中來,一起完善AvoidCrash。請Pull Requests我。
About me -- 簡書