iOS 的看门狗机制

背景

应用 100% Loss 时完全无法启动,一直崩溃。彻底切断网络连接正常启动,调试模式状态下等待时间非常久,但可以启动,并伴随 UI 微卡。强烈的预感这是线程阻塞。前一段时间被 Core Data Concurrency 折腾的够呛,看见线程问题就略有些心慌。

原因

首先看了 crash log,一如猜测,的确是卡在了主线程;意料之外的是,无数次闪退只留下了一份崩溃日志,如下所示:

WatchDog

第一次见,读了一些资料大概才算是明白了这是怎么一回事。为了避免应用陷入错误状态导致界面无响应,Apple 设计了看门狗 (WatchDog) 机制。一旦超时,强制杀死进程。在不同的生命周期,触发看门狗机制的超时时间有所不同:

生命周期 超时时间
启动 Launch 20 s
恢复 Resume 10 s
悬挂 Suspend 10 s
退出 Quit 6 s
后台 Background 10 min

首先说一说异常编码,也是寓意颇深。8badf00d = ate bad food,大概是在说看门狗吃了坏的食物所以暴走了?!异常记录则表示这并不是一次崩溃(邪魅一笑:强制退出而已)。信息一栏指出时间限制为 20 s。结合应用业务来看,表层原因在于:每次启动应用,首先进行一次模版同步,在此之前需要检测登录状况,通过 RunLoop 反复尝试直到收到响应为止。然而不幸的是,这一些都发生在主线程。

同步网络请求,主线程,超长超时时间,满足这三点,一定场景下几乎必然会触发看门狗机制。

对策

合理解决方案:

  1. 异步网络请求:优点很多,最重要的是可以让你无忧无虑安全地访问网络,而无需担心线程。
  2. 在非主线程中使用同步网络请求:如果异步运行你的网络代码比登天还难的话(也许你的应用是一个基于同步网络请求的大型移植项目),退而求次,你也可以在次级线程中运行同步代码,也可以避免触发看门狗机制。

此外,一部分情况下,例如这次遇到登录和模版同步时触发看门狗,事实上,即使在运用到模版时再次请求也是勉强可行的,因此姑且先跳过网络请求也可以。此时,还以使用一种我认为是相对比较差的方案:

  1. 通过 RunLoop 来操控一切,一旦超过既定的超时时间,就提示用户重试或者暂时先跳过网络请求。

应用的网络部分基于公司的通用框架,因此优先考虑在非主线程中进行网络请求来避免触发看门狗。

至于调试模式下为什么可以正常启动应用,完全是因为该模式下看门狗机制处于禁用状态。

此外,除了网络操作,I/O 读写文件和大规模运算等耗时任务也极有可能触发看门狗机制。合理处理线程,优化耗时任务,很大程度能避免不佳用户体验。

参考:

  1. 主线程上的同步网络请求
  2. 调试模式不发生崩溃