如何从Actor更新SwiftData存储?解决数据竞争问题
问题
如何将新的FHIRObservation保存到SwiftData中?我当前使用await self.model?.updateObservations(newObservations: fhirResources, deletedObservations: uuidsToDelete)的方式在Swift 6中引发了数据竞争问题,错误提示为:
Sending 'self'-isolated 'fhirResources' to main actor-isolated instance method 'updateObservations(newObservations:deletedObservations:)' risks causing data races between main actor-isolated and 'self'-isolated uses
同时我也不清楚如何用新获取的资源更新数据存储。我提供了FHIRObservation模型类、@MainActor修饰的UserData类以及HKManager Actor的代码。
更新1
我创建了与FHIRObservation属性相同且遵循Sendable协议的FHIRObservationWrapper,似乎解决了问题,这种方式是否可行?
解答
你的FHIRObservationWrapper方案完全可行,核心原因是Swift 6强化了并发安全检查:
- 被
@MainActor标记的updateObservations属于主线程隔离域,而你传入的fhirResources来自当前非主线程隔离域。如果FHIRObservation本身不遵循Sendable,Swift无法保证它跨隔离域传递时的线程安全性,因此抛出数据竞争警告。 - 实现遵循
Sendable的包装类,相当于明确告知Swift该类型是线程安全的,可安全跨隔离域传递,直接解决了警告问题。
SwiftData更新存储的具体实现建议
保证数据模型的并发合规性
- 如果
FHIRObservation是你自己定义的SwiftData模型,直接让它遵循Sendable更简洁:
标记为@Model final class FHIRObservation: Sendable { var id: UUID var fhirId: String var value: Double var date: Date // 其他属性 init(id: UUID, fhirId: String, value: Double, date: Date) { self.id = id self.fhirId = fhirId self.value = value self.date = date } }final的@Model类默认隐式遵循Sendable,这样可以直接传递原类型数组给主线程方法,无需额外包装。 - 如果
FHIRObservation是第三方库类无法修改,用FHIRObservationWrapper做中间层是合理选择,在包装类中完成数据转换,再传递安全的包装对象。
- 如果
规范跨Actor数据流转
在后台HKManagerActor中完成数据转换,再传递给主线程的UserData:// HKManager Actor内部代码 let wrappedObservations = fhirResources.map { FHIRObservationWrapper(from: $0) } await userData.updateObservations(newObservations: wrappedObservations, deletedObservations: uuidsToDelete)实现SwiftData的增删更新逻辑
在@MainActor修饰的updateObservations方法中处理数据同步:@MainActor func updateObservations(newObservations: [FHIRObservationWrapper], deletedObservations: [UUID]) { // 1. 删除指定UUID的记录 for uuid in deletedObservations { guard let observation = try? modelContext.fetch(FetchDescriptor<FHIRObservation>(predicate: #Predicate { $0.id == uuid })).first else { continue } modelContext.delete(observation) } // 2. 新增/更新记录 for wrapped in newObservations { // 按唯一标识(比如fhirId)查找现有记录 let existing = try? modelContext.fetch(FetchDescriptor<FHIRObservation>(predicate: #Predicate { $0.fhirId == wrapped.fhirId })).first if let existing { // 更新现有记录属性 existing.value = wrapped.value existing.date = wrapped.date // 其他属性更新 } else { // 创建新记录并插入上下文 let observation = FHIRObservation(from: wrapped) modelContext.insert(observation) } } // 保存上下文 do { try modelContext.save() } catch { print("SwiftData保存失败: \(error.localizedDescription)") } }
内容的提问来源于stack exchange,提问作者cyril
相关产品推荐
相关产品推荐

