一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
造成Crash的情况有很多种,例如:KVO问题、NSNotification线程问题、数组越界、野指针、后台任务超时、内存爆出、主线程卡顿超阀值和死锁等。如果针对每一种Crash都进行分别处理,代码会非常繁琐。而且面对各种不同的Crash,很难将所有场景全部覆盖。我们需要找到一种方案,可以拦截所有Crash情况,使得程序不会崩溃。
Crash的回调函数
创建LGUncaughtExceptionHandler类,针对Crash进行统一处理:
+ (void)installUncaughtExceptionHandler {
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
- 调用系统提供的
NSSetUncaughtExceptionHandler函数,传入LGExceptionHandlers函数地址。
处理Exception异常
当出现Crash,会自动触发LGExceptionHandlers回调函数:
void LGExceptionHandlers(NSException *exception) {
int32_t exceptionCount = OSAtomicIncrement32(&LGUncaughtExceptionCount);
// 如果太多不用处理
if (exceptionCount > LGUncaughtExceptionCount) {
return;
}
//获取调用堆栈
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
NSException *ex = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
//在主线程中,执行制定的方法, withObject是执行方法传入的参数
[[[LGUncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(lg_handleException:) withObject:ex waitUntilDone:YES];
}
- 调用
LGUncaughtExceptionHandler类的lg_handleException实例方法。
App回光返照
进入lg_handleException方法,写入以下测试代码::
- (void)lg_handleException:(NSException *)exception{
//处理报错信息,可以写入沙盒文件,下次启动时上传服务器
[self validateAndSaveCriticalApplicationData:exception];
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Crash" message:nil preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:@"继续执行" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[controller addAction:[UIAlertAction actionWithTitle:@"退出程序" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
self.dismissed = YES;
}]];
UIViewController *rootController = [UIApplication sharedApplication].keyWindow.rootViewController;
[rootController presentViewController:controller animated:true completion:nil];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!self.dismissed) {
//点击继续
for (NSString *mode in (__bridge NSArray *)allModes) {
//快速切换Mode
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
//点击退出
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
}
- 一旦出现
Crash,界面会弹窗UIAlert弹窗。之后拿到当前RunLoop,监听所有Mode; - 点击继续,循环切换
Mode; - 点击退出,改变
dismissed标记,停止while循环。
代码的使用
在AppDelegate的应用启动方法中,调用installUncaughtExceptionHandler方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[LGUncaughtExceptionHandler installUncaughtExceptionHandler];
return YES;
}
这种方式,相当于应用程序自启的Runloop的平行空间。在这个平行空间我们开启一个弹框,跟着应用程序保活,并具备响应能力,也就是App的回光返照。