前言
由於線上始終出現部分未知原因崩潰問題,遂遵循網易出的crash攔截機制,自實現了一個crash攔截工具,現已上線執行數月,累計攔截閃退···總之很多啦···
實現原理
原理網上已有很多文章闡述,這裡推薦幾個連結。
網易iOS App執行時Crash自動防護實踐 黑魔法教你讓iOS APP防住Crash
優勢:
- 封裝完善,使用方便,僅需將檔案匯入專案即可生效。
- 具備debug期crash發生的UI層級提示。
- 可和線上介面配合實現實時開關操作。
- 可自定crashinfo上傳地點(我司是直接上傳到bugly蒐集)
- 經過實際測試,已在我司多個線上APP實測有效,暫未發現有什麼奇怪的問題。
專案要點
其實從上述原理文章以及能夠了解基本的實現邏輯,只是在實現過程中也遇到了不少的坑。下面就和大家分享一下一些實現過程的坑以及為了滿足我司需求擴充的一些功能點。
- KVO
這裡劃重點
1、攔截KVO時,存在部分三方庫的不能攔截,以及系統的相機相簿無需攔截,否則會出現無效的crash提示,在我的專案已經進行了白名單過濾。如果用了一些特殊的三方,可能在使用此工具時,需要收錄一下,避免無效的crashinfo被收集。
//白名單主要針對觀察者,因為被觀察者很有可能是系統類,所以只能針對觀察者處理,如果攔截到系統的觀察者,則記錄入白名單
+ (NSArray *)kvoWhiteList
{
static NSArray *whiteList = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
whiteList = @[@"WKKVOProxy",//自己的
@"RACKVOProxy",//RAC的
@"BLYSDKManager",//bugly的
@"_YYTextKeyboardViewFrameObserver",//YYKit的
//相簿相關
@"PLManagedAlbum",
@"AVCapturePhotoOutput",
@"AVCaptureStillImageOutput",
//3.2.9新增 拍照相關
@"AVCaptureSession",
@"PLPhotoStreamAlbum",
@"AVKVODispatcher",
@"PLCloudSharedAlbum",
@"AVPlayerPropertyCache",
];//@"AVCaptureFigVideoDevice"
});
return whiteList;
}
複製程式碼
2、對KVO的攔截,需使用遞迴鎖保證執行緒安全。
wk_pthread_mutex_init_recursive(&_lock,true);
pthread_mutex_lock(&_lock);
pthread_mutex_unlock(&_lock);
複製程式碼
- Zombie
劃重點
在有殭屍物件造成崩潰時,實際是將其資料置為空,但是並不釋放它,然後將其isa指向一個可接受任何方法的中轉類中,以此來攔截掉崩潰。為了統一處理crash上報,在這裡用了動態類建立傳遞型別資訊的方式。並且.m檔案需要使用MRC,在編譯處新增-fno-objc-arc即可。
NSString *className = NSStringFromClass(selfClass);
NSString *zombieClassName = [@"WKZombie_" stringByAppendingString: className];//這一步很重要,動態生成類,如果被殭屍,則可以得知實際是哪個類產生了殭屍指標 導致崩潰
Class zombieClass = NSClassFromString(zombieClassName);
if(!zombieClass) {
zombieClass = objc_allocateClassPair([WKZombieStub class], [zombieClassName UTF8String], 0);
}
objc_destructInstance(self);//銷燬例項 相關資訊 記憶體不釋放
object_setClass(self, zombieClass);
instanceList.size();
if (instanceList.size() >= maxCount) {
id object = instanceList.front();
instanceList.pop_front();
free(object);
}
instanceList.push_back(self);
複製程式碼
- Container
在攔截NSArray以及NSDictionary的系列方法時,需要注意一下它們的實現方式是類簇實現,需要找到它們真實的類來攔截才有效。
swizzling_exchangeMethod(objc_getClass("__NSArray0"), @selector(objectAtIndex:), @selector(emptyArray_objectAtIndex:));
swizzling_exchangeMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:), @selector(arrayI_objectAtIndex:));
swizzling_exchangeMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(objectAtIndex:), @selector(singleObjectArrayI_objectAtIndex:));
複製程式碼
劃重點
在對NSMutablArray攔截時,需要特別注意其objectAtIndex的方法,需得在遵守MRC的檔案下攔截,否則會在iOS8上彈出鍵盤時,APP進入後臺產生崩潰。是必現的。所以在工具中 這個方法是單獨放到一個檔案裡面hook的,然後在編譯處為此檔案新增-fno-objc-arc。
- UI層級提示資訊
在Debug模式下,當攔截到crash時,會出現UI層級的提示,如下圖:
點選按鈕可以檢視具體的崩潰資訊,如下圖
前面title表示為崩潰的型別,後面數字為攔截的次數。
再次點選cell可定位崩潰的檔案、對應方法名、最近一次崩潰發生的時間以及在本機上這個崩潰發生的次數。
大家可能也注意到了Crash的按鈕是可以隨意拖動,以及根據你進入的大型別不同來變更提示資訊的。一個可有可無的小優化~
- CrashInfo上報
CrashInfo的收集,我們只需要關注WKCrashReport類,去實現它的一個代理即可。
@protocol WKCrashReportDelegate <NSObject>
- (void)handleCrashInfo:(WKCrashModel *)model type:(NSString *)type;
@end
複製程式碼
返回的兩個引數:WKCrashModel 以及 NSString type其功用如下:
WKCrashModel
@interface WKCrashModel : NSObject
@property (nonatomic, strong) NSString * clasName; //產生crash的類名
@property (nonatomic, strong) NSString * msg; //could be 方法名,或者其他有效資訊
@property (nonatomic, strong) NSArray * threadStack;//crash時的堆疊資訊
@property (nonatomic, assign) NSTimeInterval time;//crash時間
@property (nonatomic, strong, readonly) NSString * deviceType;//裝置資訊
@property (nonatomic, strong, readonly) NSString * systemVersion;//系統版本
@end
複製程式碼
NSString type 其返回值可能有UnrecognizedSelector,KVO,Container,Timer,NotificationCenter,Null,String,Zombie 分別代表八種攔截的crash型別
PS:如有特殊需求可自行擴充
使用方式
進入Demo地址找到WKCrashManagerDemo裡面的WKCrashSDK資料夾,拖入專案即可。 後續我會抽空將其加入cocoapods豪華午餐~
注:如從Demo中直接拖入,則預設開啟除了Zomie攔截外的其他7種型別的crash攔截。如需自定義請檢視WKCrashManager的實現檔案。
聯絡方式
如有興趣可通過郵箱357863248@qq.com一起交流進步。