因爲線上始終出現部分未知緣由崩潰問題,遂遵循網易出的crash攔截機制,自實現了一個crash攔截工具,現已上線運行數月,累計攔截閃退···總之不少啦··· git
原理網上已有不少文章闡述,這裏推薦幾個連接。github
網易iOS App運行時Crash自動防禦實踐 黑魔法教你讓iOS APP防住Crash 安全
其實從上述原理文章以及可以瞭解基本的實現邏輯,只是在實現過程當中也遇到了很多的坑。下面就和你們分享一下一些實現過程的坑以及爲了知足我司需求拓展的一些功能點。bash
這裏劃重點ide
一、攔截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;
}
複製代碼
二、對KVO的攔截,需使用遞歸鎖保證線程安全。測試
wk_pthread_mutex_init_recursive(&_lock,true);
pthread_mutex_lock(&_lock);
pthread_mutex_unlock(&_lock);
複製代碼
劃重點優化
在有殭屍對象形成崩潰時,實際是將其數據置爲空,可是並不釋放它,而後將其isa指向一個可接受任何方法的中轉類中,以此來攔截掉崩潰。爲了統一處理crash上報,在這裏用了動態類建立傳遞類型信息的方式。而且.m文件須要使用MRC,在編譯處添加-fno-objc-arc便可。ui
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);
複製代碼
在攔截NSArray以及NSDictionary的系列方法時,須要注意一下它們的實現方式是類簇實現,須要找到它們真實的類來攔截纔有效。atom
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。
在Debug模式下,當攔截到crash時,會出現UI層級的提示,以下圖:
點擊按鈕能夠查看具體的崩潰信息,以下圖
前面title表示爲崩潰的類型,後面數字爲攔截的次數。
再次點擊cell可定位崩潰的文件、對應方法名、最近一次崩潰發生的時間以及在本機上這個崩潰發生的次數。
你們可能也注意到了Crash的按鈕是能夠隨意拖動,以及根據你進入的大類型不一樣來變動提示信息的。一個無關緊要的小優化~
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一塊兒交流進步。