@Observable 类管理的 HKLiveWorkoutBuilder 的并发问题

问题描述 投票:0回答:1

我正在开发一个“HealthController”,他的职责是公开 HealthKit 的助手,并在未来跟踪各种健康包数据点,如心率、卡路里等......

到目前为止,我的设置如下所示(这是在启用完全并发的新 Swift 6 项目中)。

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 {
        // ...
      }
    }
}

Controller 被标记为@MainActor,因此可以从 swift ui 视图中调用它的函数。我现在在

builder?.beginCollection(at: startDate)
builder?.endCollection(at: endDate)
部分收到以下错误:

发送“HKLiveWorkoutBuilder”类型的主要参与者隔离值 稍后访问非隔离上下文可能会导致数据竞争

不确定这里的正确修复是什么,我尝试将构建器标记为

nonisolated
,但这似乎会导致观察生成错误。我还将每个调用包装在
MainActor.run
内,但它并没有消除错误。

ios swift watchos healthkit swift-concurrency
1个回答
0
投票

问题在于

builder
session
MainActor
隔离,但与
Sendable
隔离。您不能对其调用
nonisolated async
方法,因为这最终会将其发送给 off 主要参与者。

您应该在非隔离方法中调用

beginCollection
,因此不存在发送不可发送值的参与者跳跃。您应该只在完成后将构建器和会话分配回实例属性(将它们发送回主要参与者)。如果您在此之前将它们发送回主要参与者,那么您实际上是在主要参与者和之后的非隔离代码之间共享实例,这对于不可发送的值来说当然是不安全的。 // main actor isolated version so that you can easily call from a view func startWatchWorkout(_ name: String, _ startDate: Date) async { await startWatchWorkoutImpl(name, startDate) } private nonisolated func startWatchWorkoutImpl(_ name: String, _ startDate: Date) async { do { let configuration = HKWorkoutConfiguration() configuration.activityType = .traditionalStrengthTraining // note that I am not assigning to self.session and self.builder here let session = try HKWorkoutSession.make(healthStore: store, configuration: configuration) let builder = session?.associatedWorkoutBuilder() builder?.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: configuration) session?.startActivity(with: startDate) try await builder?.beginCollection(at: startDate) await setSessionAndBuilder(session, builder) } catch { // ... } } // assigning the properties should be done in a main actor isolated context private func setSessionAndBuilder(_ s: HKWorkoutSession?, _ b: HKLiveWorkoutBuilder?) { session = s builder = b }

	
© www.soinside.com 2019 - 2024. All rights reserved.