前言
Bugly和dispatch_once使用不当会导致UI界面堵塞。前段时间作者遇到了这样的卡壳情况,特意记录了下来。
IOS开发者一定程度上听过或者用过Bugly。这是腾讯开发的一个SDK,用于捕获应用程序崩溃。大家对dispatch_once比较熟悉,现在大部分开发人员都用它来创建单件。例如:
+ 共享实例{
static SingletonA * _ singleton = nil;
static dispatch _ once _ t once
dispatch _ once;
return _ singleton
}
但是这两者加在一起怎么会导致UI界面卡住呢?如果不是亲眼所见,我也不会相信Bugly会导致界面卡住。
现象
前几天,我们遇到了这样的情况。我们的App启动的时候有时候会卡在启动界面,过一段时间就会被系统杀死,不会有崩溃栈。这种现象让我们的发展头疼。一旦它出现,我们只能终止进程并重启应用程序,我们仍然不知道发生了什么。
调查
看到接口被卡住的第一反应是某处的死锁是否导致主线程阻塞。使用控制台。App启动时查看日志,但没有发现异常情况,在日志中查找死锁比较麻烦。
最后重新出现这种情况后,快速将手机连接到Mac,在Xcdoe中附上我们app的进度,如图:
然后暂停App进程,可以看到当前所有线程的栈。如图:
只有这样我们才知道它卡在调度_一次。我们单用有问题吗?我们知道,如果递归调用dispatch_once,就会出现死锁。示例代码如下:
+ 共享实例{
static SingletonA * _ singleton = nil;
static dispatch _ once _ t once
dispatch _ once;
return _ singleton
}
- init {
self =;
if {
;
//在这个方法中也被调用,所以会出现死锁
}
回归自我;
}
四
- 某物初始化{
;
}
很有可能这就是我们App启动时卡住的原因。所以我们开始检查dispatch_once的代码在一定条件下是否可以再次调用同一个dispatch_once,导致递归调用和死锁。
折腾了好久,也没发现dispatch_once形成递归调用的可能性。正当调查陷入僵局时,作者在日志中发现了一些信息:
***断言失败在-,/Users/whf/Desktop/WHF/XXXXXX/XXXXXX/XXXXXX管理器. m:32
怎么会有NSAssert断言日志信息?当有NSAssert断言时,App不是早就应该崩溃了吗?为什么卡住了,系统结束运行?作者处理了NSAssert的断言,不会再触发断言,然后重新调试安装App,试了很多次,不会再卡。
此时,发现了启动卡顿的触发原因,因为dispatch_once中的代码有NSAssert断言的Crash,导致主线程卡顿。
但是,为什么会这样呢?作者是一个喜欢提问的人,所以下面的问题重新出现。
问题重现
如果dispatch_once中执行的代码有崩溃,会不会造成死锁?NSAssert使用的@ try {}@ catch {}的异常机制是否改变了代码的执行顺序,导致dispatch_once死锁?
这个解释很牵强,感觉站不住脚。
作者建立了一个演示项目来测试这种情况。代码大致如下:
单线图. m文件:
@实现单线图
+ 共享实例{
static SingletonA * _ singleton = nil;
static dispatch _ once _ t once
dispatch _ once;
return _ singleton
}
- init {
self =;
if {
;
}
回归自我;
}
- 某物初始化{
NSAssert;
}
@end
MainViewController.m使用此单个实例的地方:
@实现MainViewController
- viewDidLoad {
;
;
}
@end
代码运行后,没有死锁。有必要用多线程访问吗?
立即改变了这个单一案例的使用地点
- viewDidLoad {
;
调度_异步,^{
;
});
调度_异步,^{
;
});
;
}
这个单例也在全局队列中被访问,但是没有死锁。令人失望。dispatch_once执行的代码好像有崩溃,不会造成dispatch_once死锁。
就在死锁的时候,我们的App死了之后我又看了看栈。如图:
发现Bugly后,作者立即将Bugly库添加到Demo项目中,然后再次运行代码,但仍然没有死锁。怎么回事?我仔细研究了上面的图片,这次我发现了一些新的东西。Bugly在一个非主线程中捕捉到了这个崩溃,主线程也访问了这个错误的单个实例。会不会是因为初始化这个单个实例的时候后台线程崩溃了,Bugly捕捉到了这个崩溃并在这个后台线程中处理,然后主线程访问了这个单个实例,等待这个单个实例的初始化完成?嗯,作者改变了调用singleton的地方,让主线程休眠1s,方便后台线程提前初始化singleton。
- viewDidLoad {
;
调度_异步,^{
;
});
调度_异步,^{
;
});
睡眠;//让后台线程先初始化单线图
;
}
果然,僵持!!!
主线程截图:
背景线程截图:
现在我们知道,当在使用dispatch_once初始化单例的过程中抛出异常或崩溃时,Bugly将被捕获并处理。此时,如果主线程试图再次访问这个单例,就会导致死锁。
摘要
在以下情况下,Bugly和dispatch_once会导致死锁:
当后台线程初始化dispatch_once singleton时引发异常或崩溃。
主线程也在访问这个单例。
为什么这是Bugly的弹坑?
在这种情况下,正常的进程应该是Crash,但是Bugly把这个错误变成了死锁,掩盖了问题,最后被没有Crash栈的系统杀死。要找到定位问题并不容易,尤其是不需要Crash而且是在线版的时候,解决问题就更难了。Bugly活着把我们带到了深沟里,让我们一开始在排查问题的方向上走错了路。
所以如果集成了Bugly,App经常会在未知的情况下卡住,可以检查一下这种情况。
如何避免?
虽然Bugly造成了死锁,但根本原因是我们自己的代码Crash,它只是掩盖了Crash,让我们很难排除问题。怎么才能避免?
项目中很少用到一个案例。我不喜欢有一个单一的案件来处理一切。
dispatch_once中的代码应该尽量只做初始化,不要调用很多其他方法。
调度中的代码应该尽量不抛出异常或崩溃。
把Bug交给Bugly。
好了,总结完了,我成功的把自己代码的问题扔给了Bugly ~ ~ ~ ~
请拍拍~ ~ ~
1.《bugly 腾讯Bugly巨坑:使用不当造成UI界面卡死》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《bugly 腾讯Bugly巨坑:使用不当造成UI界面卡死》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/jiaoyu/1770439.html