前言
由于線上始終出現(xiàn)部分未知原因崩潰問題,遂遵循網(wǎng)易出的crash攔截機(jī)制,自實現(xiàn)了一個crash攔截工具,現(xiàn)已上線運(yùn)行數(shù)月,累計攔截閃退···總之很多啦···

實現(xiàn)原理
原理網(wǎng)上已有很多文章闡述,這里推薦幾個鏈接。
網(wǎng)易iOS App運(yùn)行時Crash自動防護(hù)實踐
黑魔法教你讓iOS APP防住Crash
優(yōu)勢:
- 封裝完善,使用方便,僅需將文件導(dǎo)入項目即可生效。
- 具備debug期crash發(fā)生的UI層級提示。
- 可和線上接口配合實現(xiàn)實時開關(guān)操作。
- 可自定crashinfo上傳地點(我司是直接上傳到bugly搜集)
- 經(jīng)過實際測試,已在我司多個線上APP實測有效,暫未發(fā)現(xiàn)有什么奇怪的問題。
項目要點
其實從上述原理文章以及能夠了解基本的實現(xiàn)邏輯,只是在實現(xiàn)過程中也遇到了不少的坑。下面就和大家分享一下一些實現(xiàn)過程的坑以及為了滿足我司需求拓展的一些功能點。
- KVO
1、攔截KVO時,存在部分三方庫的不能攔截,以及系統(tǒng)的相機(jī)相冊無需攔截,否則會出現(xiàn)無效的crash提示,在我的項目已經(jīng)進(jìn)行了白名單過濾。如果用了一些特殊的三方,可能在使用此工具時,需要收錄一下,避免無效的才讓身體是被收集。
//白名單主要針對觀察者,因為被觀察者很有可能是系統(tǒng)類,所以只能針對觀察者處理,如果攔截到系統(tǒng)的觀察者,則記錄入白名單
+ (NSArray *)kvoWhiteList
{
static NSArray *whiteList = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
whiteList = @[@"WKKVOProxy",//自己的
@"RACKVOProxy",//RAC的
@"BLYSDKManager",//bugly的
@"_YYTextKeyboardViewFrameObserver",//YYKit的
//相冊相關(guān)
@"PLManagedAlbum",
@"AVCapturePhotoOutput",
@"AVCaptureStillImageOutput",
//3.2.9添加 拍照相關(guān)
@"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
在有僵尸對象造成崩潰時,實際是將其數(shù)據(jù)置為空,但是并不釋放它,然后將其isa指向一個可接受任何方法的中轉(zhuǎn)類中,以此來攔截掉崩潰。為了統(tǒng)一處理crash上報,在這里用了動態(tài)類創(chuàng)建傳遞類型信息的方式。并且.m文件需要使用mrl,在編譯處添加-fno-objc-arc即可。
NSString *className = NSStringFromClass(selfClass);
NSString *zombieClassName = [@"WKZombie_" stringByAppendingString: className];//這一步很重要,動態(tài)生成類,如果被僵尸,則可以得知實際是哪個類產(chǎn)生了僵尸指針 導(dǎo)致崩潰
Class zombieClass = NSClassFromString(zombieClassName);
if(!zombieClass) {
zombieClass = objc_allocateClassPair([WKZombieStub class], [zombieClassName UTF8String], 0);
}
objc_destructInstance(self);//銷毀實例 相關(guān)信息 內(nèi)存不釋放
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的系列方法時,需要注意一下它們的實現(xiàn)方式是類簇實現(xiàn),需要找到它們真實的類來攔截才有效。
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進(jìn)入后臺產(chǎn)生崩潰。是必現(xiàn)的。所以在工具中 這個方法是單獨放到一個文件里面hook的,然后在編譯處為此文件添加-fno-objc-arc。
- UI層級提示信息
在Debug模式下,當(dāng)攔截到crash時,會出現(xiàn)UI層級的提示,如下圖:
1.png
點擊按鈕可以查看具體的崩潰信息,如下圖
2.png
前面title表示為崩潰的類型,后面數(shù)字為攔截的次數(shù)。
再次點擊cell可定位崩潰的文件、對應(yīng)方法名、最近一次崩潰發(fā)生的時間以及在本機(jī)上這個崩潰發(fā)生的次數(shù)。
3.png
4.png
大家可能也注意到了Crash的按鈕是可以隨意拖動,以及根據(jù)你進(jìn)入的大類型不同來變更提示信息的。一個可有可無的小優(yōu)化~
- CrashInfo上報
CrashInfo的收集,我們只需要關(guān)注WKCrashReport類,去實現(xiàn)它的一個代理即可。
@protocol WKCrashReportDelegate <NSObject>
- (void)handleCrashInfo:(WKCrashModel *)model type:(NSString *)type;
@end
返回的兩個參數(shù):WKCrashModel 以及 NSString type其功用如下:
WKCrashModel
@interface WKCrashModel : NSObject
@property (nonatomic, strong) NSString * clasName; //產(chǎn)生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;//設(shè)備信息
@property (nonatomic, strong, readonly) NSString * systemVersion;//系統(tǒng)版本
@end
NSString type
其返回值可能有UnrecognizedSelector,KVO,Container,Timer,NotificationCenter,Null,String,Zombie
分別代表八種攔截的crash類型
PS:如有特殊需求可自行擴(kuò)充
使用方式
Demo地址
進(jìn)入Demo地址找到WKCrashManagerDemo里面的WKCrashSDK文件夾,拖入項目即可。
后續(xù)我會抽空將其加入cocoapods豪華午餐~
注:如從Demo中直接拖入,則默認(rèn)開啟除了Zomie攔截外的其他7種類型的crash攔截。如需自定義請查看WKCrashManager的實現(xiàn)文件。
聯(lián)系方式
如有興趣可通過郵箱357863248@qq.com一起交流進(jìn)步。



