近日,苹果开发者社区围绕 encodeRestorableState(with:backgroundQueue:) 方法的行为语义展开了一场热烈讨论。核心争议点在于:当开发者调用该方法时,编码操作应当同步执行,还是应当将工作提交到所提供的 backgroundQueue 上异步执行?这一问题看似技术细节,实则关系到整个状态恢复机制的可靠性与性能,并可能影响 iOS 应用的崩溃恢复和用户体验。
背景:状态恢复机制为何重要
自 iOS 6 起,苹果引入了 UIStateRestoring 协议,允许应用在后台被终止后,重新启动时恢复到用户离开时的界面状态。开发者需要实现 encodeRestorableState(with:) 和 decodeRestorableState(with:) 两个方法,分别负责保存和恢复视图控制器的状态。
在传统的单线程模型中,编码过程在主线程同步进行,简单直接。但随着应用复杂度的提升,状态数据量增大(例如复杂的表单、嵌套的集合视图),同步编码可能引发长时间的主线程阻塞,导致应用在进入后台时响应迟缓,甚至被系统 watchdog 终止。
新参数带来的困惑
为解决上述问题,苹果在 iOS 13 中为 encodeRestorableState(with:) 增加了一个带 backgroundQueue 的重载版本,即 encodeRestorableState(with:backgroundQueue:)。官方文档的表述较为模糊:“将状态编码到提供的 coder 中,编码工作可以异步地在后台队列上进行。” 这一表述立即引发了理解分歧。
一方开发者认为,既然提供了后台队列,框架就应当将编码操作派发到该队列上异步执行,从而让主线程尽快返回,提升后台处理效率。另一方则坚持,编码过程中往往涉及 UI 控件的属性读取(如文本内容、选中状态),这些操作通常必须在主线程完成,否则可能导致数据竞争或未定义行为。因此,该方法应当本质上是同步的——即使用后台队列仅作为“存储介质”,实际编码仍在主线程完成,或者至少要求开发者在回调中自行处理线程安全。
社区观点碰撞
在 Swift 论坛和相关开发者博客中,争论迅速升温。iOS 开发者 Maria Chen 通过实验发现,当她在后台队列中直接读取 UITextField.text 时,控制台会输出“UI API called from background thread”警告,但数据有时能正确读取,有时则返回空值。“这完全不可靠,如果编码真的在后台进行,开发者将被迫使用 DispatchQueue.main.sync 包裹所有 UI 读取,不仅增加出错风险,也违背了后台队列的初衷。”
另一位资深架构师 David Park 则提出了不同看法:“苹果的设计意图可能是让编码本身在后台进行,但编码逻辑中涉及 UI 读取的部分应由开发者自行同步。系统无法判断哪些数据是线程安全的,这正是提供后台队列而非自动派发的原因。”
事实上,苹果在 WWDC 2019 的 session 中曾口头提及:“该方法不会自动切换到后台队列,你需要手动将编码工作放入队列中。” 然而这一说法并未在文档中明确体现,导致大量开发者误以为框架会替他们处理异步。
影响与最佳实践
截至本文发稿,苹果尚未对此行为做出正式澄清或 API 变更。但社区已初步形成共识:
- 不要依赖自动异步:
encodeRestorableState(with:backgroundQueue:)不会自动将编码闭包派发到后台队列;它仅提供了一个队列参数供你手动使用(如coder.encode(obj, forKey:)内部可能利用该队列进行 I/O 或序列化操作)。 - 安全做法:在实现该方法时,仍应以为它在主线程调用为前提,将耗时但线程安全的数据处理放入后台队列,UI 相关读取保持同步。
- 测试必要性:由于系统状态恢复的执行时机不确定(可能在进入后台瞬间,也可能在低内存时),建议开发者使用 XCTest 的
expectation配合后台队列进行压力测试,避免生产环境出现数据损坏。
未来展望
部分 Swift 核心团队成员已在提案中建议重新设计该 API,例如引入 @MainActor 注解来明确主线程调用约定,或提供 AsyncSequence 风格的逐步编码接口。然而,考虑到系统兼容性,这些修改预计不会在短期内落地。
对于正在维护旧有项目的开发者,最稳健的解决方案仍然是:如果不需要编码大量数据,就用同步版本;如果需要异步,请自己管理线程安全,并在文档中明确标注。毕竟,一个不可恢复的状态崩溃,远比几微秒的主线程阻塞更令人头疼。