前言

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/shehui/1770445.html