RunLoop总结:RunLoop的应用场景(五)

2024-02-05 15:38
文章标签 总结 应用 场景 runloop

本文主要是介绍RunLoop总结:RunLoop的应用场景(五),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天要介绍的RunLoop应用场景感觉很酷炫,我们可能不常用到,但是对于做Crash 收集的 SDK可能会用得比较频繁吧。相比关于RunLoop 可以让应用起死回生,大家都听说过,可是怎么实现呢?今天我就来实际试验一下。

资料

  • sunnyxx线下分享RunLoop (这是一份关于线下分享与讨论RunLoop的视频,备用地址:https://pan.baidu.com/s/1pLm4Vf9)
  • 漫谈iOS Crash收集框架(简单介绍了下iOS 中Crash 的一些知识。)
  • IOS程序异常crash捕获与拦截 (我下面的Demo 就是在这部分代码上做了简化,以方便理解)

原理

iOS应用崩溃,常见的崩溃信息有EXC_BAD_ACCESSSIGABRT XXXXXXX,而这里分为两种情况,一种是未被捕获的异常,我们只需要添加一个回调函数,并在应用启动时调用一个 API即可;另一种是直接发送的 SIGABRT XXXXXXX,这里我们也需要监听各种信号,然后添加回调函数。

针对情况一,其实我们都见过。我们在收集App崩溃信息时,需要添加一个函数 NSSetUncaughtExceptionHandler(&HandleException),参数 是一个回调函数,在回调函数里获取到异常的原因,当前的堆栈信息等保存到 dump文件,然后供下次打开App时上传到服务器。

其实,我们在HandleException回调函数中,可以获取到当前的RunLoop,然后获取该RunLoop中的所有Mode,手动运行一遍。

针对情况二,首先针对多种要捕获的信号,设置好回调函数,然后也是在回调函数中获取RunLoop,然后拿到所有的Mode,手动运行一遍。

代码实现

第一步,我创建了一个处理类,并添加一个单例方法。(代码见末尾的Demo)

第二步,在单例中对象实例化时,添加 异常捕获 和 signal 处理的 回调函数。

- (void)setCatchExceptionHandler
{// 1.捕获一些异常导致的崩溃NSSetUncaughtExceptionHandler(&HandleException);// 2.捕获非异常情况,通过signal传递出来的崩溃signal(SIGABRT, SignalHandler);signal(SIGILL, SignalHandler);signal(SIGSEGV, SignalHandler);signal(SIGFPE, SignalHandler);signal(SIGBUS, SignalHandler);signal(SIGPIPE, SignalHandler);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第三步,分别实现 异常捕获的回调 和 signal 的回调。

void HandleException(NSException *exception)
{// 获取异常的堆栈信息NSArray *callStack = [exception callStackSymbols];NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];[userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];CrashHandler *crashObject = [CrashHandler sharedInstance];NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}void SignalHandler(int signal)
{// 这种情况的崩溃信息,就另某他法来捕获吧NSArray *callStack = [CrashHandler backtrace];NSLog(@"信号捕获崩溃,堆栈信息:%@",callStack);CrashHandler *crashObject = [CrashHandler sharedInstance];NSException *customException = [NSException exceptionWithName:kSignalExceptionNamereason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

第四步,添加让应用起死回生的 RunLoop 代码

- (void)handleException:(NSException *)exception
{NSString *message = [NSString stringWithFormat:@"崩溃原因如下:\n%@\n%@",[exception reason],[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];NSLog(@"%@",message);UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"程序崩溃了"message:@"如果你能让程序起死回生,那你的决定是?"delegate:selfcancelButtonTitle:@"崩就蹦吧"otherButtonTitles:@"起死回生", nil];[alert show];CFRunLoopRef runLoop = CFRunLoopGetCurrent();CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);while (!ignore) {for (NSString *mode in (__bridge NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);}}CFRelease(allModes);NSSetUncaughtExceptionHandler(NULL);signal(SIGABRT, SIG_DFL);signal(SIGILL, SIG_DFL);signal(SIGSEGV, SIG_DFL);signal(SIGFPE, SIG_DFL);signal(SIGBUS, SIG_DFL);signal(SIGPIPE, SIG_DFL);if ([[exception name] isEqual:kSignalExceptionName]) {kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);} else {[exception raise];}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

因为我这里弄了一个AlertView弹窗,所以必须要回到主线程来处理。
实际上,RunLoop 相关的代码:

 CFRunLoopRef runLoop = CFRunLoopGetCurrent();CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);while (!ignore) {for (NSString *mode in (__bridge NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);}}CFRelease(allModes);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

完全可以写在 上面的 HandleException 回调 和 SignalHandler回调中。

第五步,写一段会导致崩溃的代码

我是在ViewController 中添加了一个点击事件,弄了一个数组越界的Bug:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{NSArray *array =[NSArray array];NSLog(@"%@",[array objectAtIndex:1]);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

动态效果图:

遇到数组越界,应用依然没崩溃

sunnyxx 称之为回光返照,为什么呢?
我再一次点击视图,应用依然还是崩溃了,只能防止第一次崩溃。
我测试了,确实是第二次应用崩溃,未能起死回生。

文中的示例代码都来自:RunLoopDemos中的RunLoopDemo04


转自:http://blog.csdn.net/u011619283/article/details/53673255

这篇关于RunLoop总结:RunLoop的应用场景(五)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/681377

相关文章

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Kubernetes常用命令大全近期总结

《Kubernetes常用命令大全近期总结》Kubernetes是用于大规模部署和管理这些容器的开源软件-在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用Kubernetes(有时被称为“... 目录前言Kubernetes 的工作原理为什么要使用 Kubernetes?Kubernetes常用命令总

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Python中实现进度条的多种方法总结

《Python中实现进度条的多种方法总结》在Python编程中,进度条是一个非常有用的功能,它能让用户直观地了解任务的进度,提升用户体验,本文将介绍几种在Python中实现进度条的常用方法,并通过代码... 目录一、简单的打印方式二、使用tqdm库三、使用alive-progress库四、使用progres

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或