因为线上始终出现部分未知缘由崩溃问题,遂遵循网易出的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一块儿交流进步。