iOS 的看门狗机制
背景
当应用的网络状况极差 (100% Loss) 时完全无法启动,一直崩溃。彻底切断网络连接正常启动,调试模式状态下等待时间非常久,但可以启动,并伴随 UI 微卡。强烈的预感这是线程阻塞。前一段时间被 Core Data Concurrency 折腾的够呛,看见线程问题就略有些心慌。
原因
首先看了 crash log,一如猜测,的确是卡在了主线程;意料之外的是,无数次闪退只留下了一份崩溃日志,如下所示:
第一次见,读了一些资料大概才算是明白了这是怎么一回事。为了避免应用陷入错误状态导致界面无响应,Apple 设计了看门狗 (WatchDog) 机制。一旦超时,强制杀死进程。在不同的生命周期,触发看门狗机制的超时时间有所不同:
生命周期 | 超时时间 |
---|---|
启动 Launch | 20 s |
恢复 Resume | 10 s |
悬挂 Suspend | 10 s |
退出 Quit | 6 s |
后台 Background | 10 min |
首先说一说异常编码,也是寓意颇深。8badf00d
= ate bad food,大概是在说看门狗吃了坏的食物所以暴走了?!异常记录则表示这并不是一次崩溃(邪魅一笑:强制退出而已)。信息一栏指出时间限制为 20 s。结合应用业务来看,表层原因在于:每次启动应用,首先进行一次模版同步,在此之前需要检测登录状况,通过 RunLoop 反复尝试直到收到响应为止。然而不幸的是,这一些都发生在主线程。
同步网络请求,主线程,超长超时时间,满足这三点,一定场景下几乎必然会触发看门狗机制。
对策
合理解决方案:
- 异步网络请求:优点很多,最重要的是可以让你无忧无虑安全地访问网络,而无需担心线程。
- 在非主线程中使用同步网络请求:如果异步运行你的网络代码比登天还难的话(也许你的应用是一个基于同步网络请求的大型移植项目),退而求次,你也可以在次级线程中运行同步代码,也可以避免触发看门狗机制。
此外,一部分情况下,例如这次遇到登录和模版同步时触发看门狗,事实上,即使在运用到模版时再次请求也是勉强可行的,因此姑且先跳过网络请求也可以。此时,还以使用一种我认为是相对比较差的方案:
- 通过 RunLoop 来操控一切,一旦超过既定的超时时间,就提示用户重试或者暂时先跳过网络请求。
应用的网络部分基于公司的通用框架,因此优先考虑在非主线程中进行网络请求来避免触发看门狗。
至于调试模式下为什么可以正常启动应用,完全是因为该模式下看门狗机制处于禁用状态。
此外,除了网络操作,I/O 读写文件和大规模运算等耗时任务也极有可能触发看门狗机制。合理处理线程,优化耗时任务,很大程度能避免不佳用户体验。