在触发完成处理程序之前如何最好地确保我拥有所有数据?

时间:2018-08-14 06:06:23

标签: ios swift

我对如何最好地获取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返回之前,如何确保我在所有圈中都有心率数据?

1 个答案:

答案 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)
    }
}