iOS 中程序崩溃时,如果不做任何处理会很快退出。如果我们想要在崩溃时将崩溃信息如函数调用堆栈等保存在本地沙盒或者上传给服务端,就像 bug 收集分析的第三方那样。我们该怎么办呢?
首先我们要在AppDelegate.m中设置接收全局异常并且设置全局异常处理的函数。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSSetUncaughtExceptionHandler(&exceptionHandler);//设置全局异常处理
return YES;
}
//全局异常处理函数
void exceptionHandler(NSException * exception)
{
NSLog(@"CRASH: %@", exception);//打印奔溃异常
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);//打印奔溃异常的函数调用堆栈信息
NSLog(@"Oh, app, you will die!");
}
按照上面设置完,发生崩溃时就会执行exceptionHandler函数,之后程序就退出了。此时我们在这个函数里面将崩溃信息写入沙盒或上传给服务端是不是就可以了呢?
非也!只能说时机是对的,但是如果我们还按照不影响主线程将耗时操作放入到子线程异步来处理的话,程序是不会等到子线程任务处理完毕再退出的,也就是说崩溃信息还没有写入沙盒或上传给服务端就退出了。那怎么办呢?
正如上面所说,异步来处理这些操作不行,那我们就同步处理呗!这样就不会没完成操作就退出了。方案确实是这样的。同步任务的方案有很多,这里使用的是dispatch_semaphore(不了解的同学可以自行百度)。
代码如下:
//全局异常处理函数
void exceptionHandler(NSException * exception)
{
NSLog(@"CRASH: %@", exception);//打印奔溃异常
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);//打印奔溃异常的函数调用堆栈信息
NSLog(@"Oh, app, you will die!");
//将信息保存本地或者上传到服务器(异步任务的时候要记得将任务同步起来,要不等不到异步任务完成程序就退出了,可以使用 dispatch_semophore来阻塞当前线程)。
//也可以考虑获取当前 runloop 添加任务来保活线程,任务完成后程序退出
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//模拟上传信息或写入沙盒操作
[[AppDelegate new] requestTaskOrWriteToFile:^{
NSLog(@"发送信号使得信号量加一");
dispatch_semaphore_signal(semaphore);
}];
dispatch_wait(semaphore, DISPATCH_TIME_FOREVER);//阻塞当前线程直到 semaphore 大于 0 时继续执行下面代码完之后退出
NSLog(@"我可以走到这里了,之后程序就退出了");
}
//模拟进行网络请求或写入沙盒操作
- (void)requestTaskOrWriteToFile:(void(^)(void))result
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"子线程开始处理耗时操作");
sleep(5);
NSLog(@"子线程结束处理耗时操作");
result();
});
}
至此程序奔溃时通过dispatch_semophore延迟退出来处理一些操作就讲完了!是不是很简单?
这样的思路还可以用到其他地方,比如你可以在 APP 将要 terminal 时来进行一些操作处理如保存用户此时一些配置到本地沙盒中。