SwiftData|使用 ModelActor 存在数据更新延迟问题

问题案例

在 MONO 记账中,在账单详情页面修改账单的报销状态:待报销或已报销。修改之后,报销统计数据无法正确及时的更新(有时更新,有时不更新,结果不可测)。

排查发现原因如下:

  1. 在 ReimbursementView 组件中,更新报销状态时只使用 Picker 组件,未显式调用 modelContext.save() 进行手动保存。
  2. 统计数据在 ReimbursementViewModel 中计算,通过调用 actor 中的方法进行计算。
ReimbursementView
ReimbursementViewModel

和 Task.detach 无关

既然在 ReimbursementViewModel 中,通过 Task.detached 在后台调用 actor 方法执行计算任务。如果不使用 Task.detached ,该问题是否仍然存在?

经过测试,即使修改为 Task,只要未显示调用 modelContext.save() 方法保存数据,该问题就仍然存在。一旦调用 modelContext.save(),该问题就消失。

因此和 Task.detach 无关。

问题本质探索

因为 ModelActor 始终在独立的 ModelContext 中操作

虽然可以整个 App 使用一个 ModelContainer(DataModel.shared.modelContainer),这样它们共享同一个 ModelContainer 的底层存储,但每个线程/actor 有自己的 ModelContext。

SwiftData 有自动保存机制,但自动保存存在延迟且时间不可预测。后台查询可能在保存前执行,导致查询到旧数据。

多个论坛或文章提到该问题:

解决方案

手动保存数据(有效)

在执行关键数据的修改之后,如果要立即触发重新计算统计数据,务必使用 modelContext.save() 手动保存数据即可。

添加延迟(无效)

既然 ModelContext 数据同步存在延迟,我们能不能在 ViewModel 中添加更长的等待时间(防抖时间),等待数据同步完成后再查询呢?

测试

  • 防抖时间设置为 100ms,数据仍然未同步
  • 防抖时间设置为 300ms,数据仍然未同步
  • 防抖时间设置为 1000ms,数据仍然未同步

说明,此方法行不通。