【发布时间】:2021-12-01 08:01:28
【问题描述】:
我正在尝试在 HKWorkoutEvent 定义的时间间隔内查询 HealthKit 的心率值和步数,以填充我定义的用于存储多个变量的自定义本地模型,它定义如下。
struct SGWorkoutEvent: Identifiable {
let id = UUID()
let type: HKWorkoutEventType
let splitActiveDurationQuantity: HKQuantity?
let splitDistanceQuantity: HKQuantity?
let totalDistanceQuantity: HKQuantity?
let splitMeasuringSystem: HKUnit
let steps: HKQuantity?
let heartRate: HKQuantity?
}
除steps 和heartRate 之外的所有属性都可以从HKWorkoutEvent 中提取。但是,我正在尝试构建一个组合管道,它可以让我创建一个发布者数组,以并行查询心率、步数并传递锻炼事件,因此在sink 我收到一个包含这些值的 3 元素元组所以我可以填充上面的模型。我目前拥有的如下,
// Extract the workout's segments (defined automatically by an Apple Watch)
let workoutSegments = (workout.workoutEvents ?? []).filter({ $0.type == .segment })
// For each of the workout segments defined above create a HKStatisticQuery that starts on the interval's
// beginning and ends on the interval's end so the HealthKit query is properly defined to be
// executed between that interval.
let segmentsWorkoutPublisher = Publishers.MergeMany(workoutSegments.map({ $0.dateInterval }).map({
healthStore.statistic(for: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, with: .discreteAverage, from: $0.start, to: $0.end)
}))
.assertNoFailure()
// Do the same logic as above in `segmentsWorkoutPublisher` but for steps
let stepsPublisher = Publishers.MergeMany(workoutSegments.map({ $0.dateInterval }).map({
healthStore.statistic(for: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, with: .cumulativeSum, from: $0.start, to: $0.end)
}))
.assertNoFailure()
Publishers.Zip3(workoutSegments.publisher, stepsPublisher, segmentsWorkoutPublisher)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { pace, steps, hrs in
let d = SGWorkoutEvent(type: pace.type,
splitActiveDurationQuantity: pace.splitDuration,
splitDistanceQuantity: pace.splitDistance,
totalDistanceQuantity: pace.totalDistanceQuantity,
splitMeasuringSystem: pace.splitMeasuringSystem,
steps: steps.sumQuantity(),
heartRate: hrs.averageQuantity())
self.paces.append(d)
})
.store(in: &bag)
HKHealthStore.statistic(for:...) 只不过是在 HKHealthStore 扩展上定义的 HKStatisticsQuery 的组合包装器,见下文。
public func statistic(for type: HKQuantityType, with options: HKStatisticsOptions, from startDate: Date, to endDate: Date, _ limit: Int = HKObjectQueryNoLimit) -> AnyPublisher<HKStatistics, Error> {
let subject = PassthroughSubject<HKStatistics, Error>()
let predicate = HKStatisticsQuery.predicateForSamples(withStart: startDate, end: endDate, options: [.strictEndDate, .strictStartDate])
let query = HKStatisticsQuery(quantityType: type, quantitySamplePredicate: predicate, options: options, completionHandler: { (query, statistics, error) in
guard error == nil else {
hkCombineLogger.error("Error fetching statistics \(error!.localizedDescription)")
return
}
subject.send(statistics!)
subject.send(completion: .finished)
})
self.execute(query)
return subject.eraseToAnyPublisher()
}
我在这里看到的是某种竞赛条件,其中检索到的步数和心率没有同时返回广告。结果,我看到了没有意义的值,例如 5' 200steps 的 1K 拆分和相同持续时间 700steps 的另一个。实际情况应该是这两个间隔应该显示一个大约 150 的值,但似乎我可能没有使用正确的组合运算符。
我希望看到的预期行为是Publishers.Zip 上的每个发布者让每个 3 项元组按顺序(第一个间隔,第二个间隔......)完成其查询,而不是这种不可复制的竞争条件.
为了尝试提供更多上下文,我认为这类似于为不同的时间戳创建一个包含温度、湿度和下雨概率的模型,然后查询三个不同的 API 端点以检索三个不同的值并将它们合并到模型中。
【问题讨论】:
-
当您的代码无法编译时,很难提供帮助。我建议制作一个具有类似功能的示例项目并将其发布。也就是说,乍一看,您的
statistic方法做得太多了。将其缩减为 3-4 个参数并进行重构。此外,Array有一个发布者。而不是使用 MergeMany 尝试workoutSegments.publisher并玩弄它。