You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

由@Observable类管理HKLiveWorkoutBuilder的并发问题排查

解决HealthKit HKLiveWorkoutBuilder并发数据竞争警告

问题场景

我正在开发HealthController类,用于封装HealthKit的辅助方法,后续将支持心率、卡路里等指标追踪。当前基于Swift 6完整并发模式实现,类标记为@MainActor @Observable以适配SwiftUI视图调用,但在调用builder?.beginCollection(at:)和builder?.endCollection(at:)时出现以下警告:

Sending main actor-isolated value of type 'HKLiveWorkoutBuilder' with later accesses to nonisolated context risks causing data races

尝试过将builder标记为nonisolated(与@Observation特性冲突)、用MainActor.run包裹调用,均未解决问题。

原始代码

import HealthKit
import SwiftUI

@MainActor @Observable
final class HealthController {
    // State
    let store = HKHealthStore()
    var session: HKWorkoutSession?
    var builder: HKLiveWorkoutBuilder?

    // Other code for authorization, configuration defaults etc...

    func startWatchWorkout(_ name: String, _ startDate: Date) async {
      do {
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .traditionalStrengthTraining
        session = try HKWorkoutSession(healthStore: store, configuration: configuration)
        builder = session?.associatedWorkoutBuilder()
        builder?.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: configuration)
        session?.startActivity(with: startDate)
        try await builder?.beginCollection(at: startDate)
      } catch {
        // ...
      }
    }

    func endWatchWorkout(_ endDate: Date) async {
      do {
        session?.end()
        try await builder?.endCollection(at: endDate)
        session = nil
        builder = nil
      } catch {
        // ...
      }
    }
}

修复方案

方案1:使用局部变量捕获builder引用

核心思路是将builder赋值给MainActor上下文中的局部变量,避免编译器担心属性被跨线程访问。修改startWatchWorkout和endWatchWorkout方法:

func startWatchWorkout(_ name: String, _ startDate: Date) async {
  do {
    let configuration = HKWorkoutConfiguration()
    configuration.activityType = .traditionalStrengthTraining
    session = try HKWorkoutSession(healthStore: store, configuration: configuration)
    builder = session?.associatedWorkoutBuilder()
    builder?.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: configuration)
    session?.startActivity(with: startDate)
    
    // 捕获局部变量,消除逃逸风险
    let localBuilder = builder
    try await localBuilder?.beginCollection(at: startDate)
  } catch {
    // ...
  }
}

func endWatchWorkout(_ endDate: Date) async {
  do {
    session?.end()
    
    // 捕获局部变量,消除逃逸风险
    let localBuilder = builder
    try await localBuilder?.endCollection(at: endDate)
    
    session = nil
    builder = nil
  } catch {
    // ...
  }
}

方案2:标记session和builder为非隔离并忽略观察

如果session和builder不需要被SwiftUI视图观察,可以用@ObservationIgnored和nonisolated修饰,彻底隔离并发上下文:

@MainActor @Observable
final class HealthController {
    let store = HKHealthStore()
    // 标记为非隔离并忽略视图观察
    @ObservationIgnored nonisolated var session: HKWorkoutSession?
    @ObservationIgnored nonisolated var builder: HKLiveWorkoutBuilder?

    // 其余代码不变...
}

这种方法更彻底,因为HKWorkoutSession和HKLiveWorkoutBuilder本身不是MainActor绑定的类型,将它们设为非隔离更符合HealthKit的设计,同时@ObservationIgnored解决了与@Observable的冲突。


内容的提问来源于stack exchange,提问作者Ilja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 06:28:10