我对如何最好地获取HealthKit数据(特别是心率记录)感到困惑。我的问题是由于竞争导致的,这是由于异步调用和完成处理程序引起的。
我的应用程序记录了锻炼会话,最终将数据推送到远程服务器以进行清洁和分析。我想在会话同步请求中包括心率数据。
一个会话由GPS组成,其他传感器数据分为几圈。
当我开始同步会话时,我会像下面这样调用以下函数:
fetchSessionLapTimes(session: session) { (storedSessionLapTime, error) in
//handle the received lap time data
//At this point, I expect my storedSessionLapTime variable to
//contain my session lap times, and heart rate data
})
我的fetchSessionLapTimes
函数的定义如下:
func fetchSessionLapTimes(session: Session, withCompletion complete: ((_ storedSessionLapTime: [SessionLapTime], _ error: Error?) -> Void)!) {
var storedSessionLapTime = [SessionLapTime]()
managedObjectContext.performAndWait {
//get session lap times from Core Data
//this code works fine, as expected and populates
//storedSessionLapTime
}
//now this is where my problem is. At this point I want to extract
//heart rate data for each lap time
let healthStoreService = HealthStoreService()
for sessionLapTime in storedSessionLapTime {
let start = Date(timeIntervalSince1970: sessionLapTime.lapStartDate)
let end = Date(timeIntervalSince1970: sessionLapTime.lapEndDate)
healthStoreService.fetchWorkoutData(startDate: start, endDate: end) { (success, error) in
complete(storedSessionLapTime, nil)
}
}
}
我的fetchSessionLapTimes
函数定义如下:
func fetchWorkoutData(startDate: Date, endDate: Date, completion: ((_ success: Bool, _ error: Error?) -> Void)!) {
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let query = HKSampleQuery(sampleType: hrType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: nil) {
query, results, error in
guard let samples = results as? [HKQuantitySample] else {
completion(false, error)
fatalError("Error fetching workout data: \(error!.localizedDescription)");
}
if samples.count < 1 {
self.debug.log(tag: "HealthStoreService", content: "No workout data found")
completion(true, nil)
return
}
DispatchQueue.main.async {
for sample in samples {
self.debug.log(tag: "HealthStoreService", content: "\(sample.quantity.doubleValue(for: self.hrUnit))")
}
completion(true, nil)
return
}
}
healthStore.execute(query)
}
如您所见,该函数也是异步的。如果运行此代码,则不会获得所有圈的心率数据。 在允许fetchSessionLapTimes
返回之前,如何确保我在所有圈中都有心率数据?
答案 0 :(得分:1)
您可以将所有fetchWorkoutData
任务从for
循环添加到DispatchGroup
。当它们全部完成时,您将收到通知,因此您可以随后调用函数的完成。这是一个示例:
func fetchSessionLapTimes(session: Session, withCompletion complete: ((_ storedSessionLapTime: [SessionLapTime], _ error: Error?) -> Void)!) {
var storedSessionLapTime = [SessionLapTime]()
managedObjectContext.performAndWait {
//
}
// Here you were putting async calls to a for loop and couldn't tell when all were completed
// DispatchGroup is made exactly to handle this
let dispatchGroup = DispatchGroup()
let healthStoreService = HealthStoreService()
for sessionLapTime in storedSessionLapTime {
let start = Date(timeIntervalSince1970: sessionLapTime.lapStartDate)
let end = Date(timeIntervalSince1970: sessionLapTime.lapEndDate)
// Adding the task to group
dispatchGroup.enter()
healthStoreService.fetchWorkoutData(startDate: start, endDate: end) { (success, error) in
// notifying when this particular task finishes
dispatchGroup.leave()
}
}
// completion is called only when all of the DispatchGroup tasks are finished
dispatchGroup.notify(queue: .main) {
// call completion here because all tasks completed
complete(storedSessionLapTime, nil)
}
}