I have this code to get data from the health store. I am looping through information for each of the different sample types that I want to get from the health store in the fetchSampleData function. I am expecting the function to loop through and get all of the different kinds of samples and then use the completion to send that data back to the getData function. I then want to use that data in my code.
func getData() {
// Data types and units for health kit queries
let sampleInfo = [
("heartRate", HKObjectType.quantityType(
forIdentifier: .heartRate), "count/min"),
("heartRateVariabilitySDNN", HKObjectType.quantityType(
forIdentifier: .heartRateVariabilitySDNN), "s"),
("stepCount", HKObjectType.quantityType(
forIdentifier: .stepCount), "count"),
]
self.fetchSampleData(
sampleInfo: sampleInfo
) { samplesDict in
let sampleCount = samplesDict.count
print("Sample count: \(sampleCount)")
print(samplesDict)
if (sampleCount > 0) {
// code to do stuff with samplesDict ...
}
}
}
public func fetchSampleData(
sampleInfo: [(String, HKQuantityType?, String)],
completion: @escaping ( _ samples:
Dictionary<String, Dictionary<Date, Double>>) -> Void
) {
let numberOfDataTypes = sampleInfo.count
var resultsDict: [String: [Date: Double]] = [:]
for i in 0..<numberOfDataTypes {
let dataTypeName = sampleInfo[i].0
let sampleType = sampleInfo[i].1!
let unitString = sampleInfo[i].2
var dataTypeDictionary = [Date:Double]()
// Predicate for specifying start and end dates for the query
let predicate = HKQuery
.predicateForSamples(
withStart: Date.distantPast,
end: Date.now,
options: .strictEndDate)
// Set sorting by date.
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierStartDate,
ascending: false)
// Create the query
let query = HKSampleQuery(
sampleType: sampleType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: [sortDescriptor]) { (_, results, error) in
guard error == nil else {
print("Error: \(error!.localizedDescription)")
return
}
print("~~- \(results?.count) \(dataTypeName) samples returned")
for sample in results ?? [] {
let data = sample as! HKQuantitySample
let unit = HKUnit(from: unitString)
let sampleVal = data.quantity.doubleValue(for: unit)
let dateStart = data.startDate
dataTypeDictionary[dateStart] = sampleVal
}
resultsDict[dataTypeName] = dataTypeDictionary
}
healthStore.execute(query)
}
completion(resultsDict)
}
But when I run this code I am getting this for an output:
Sample count: 0
[:]
~~- Optional(103) heartRateVariabilitySDNN samples returned
~~- Optional(846) stepCount samples returned
~~- Optional(3127) activeEnergyBurned samples returned
~~- Optional(4470) heartRate samples returned
~~- Optional(2913) basalEnergyBurned samples returned
So the code that i was assuming would run after the fetchSampleData function was done seems to be running before the data is even returned. Is there a way that I could tell the program to only run the code involving the returned samplesDict data if the fetchSampleData has finished getting that data?
Your fetchSampleData
function loops through numberOfDataTypes
, firing off an async query to the health store each time through the loop. Then, before any of those async calls can complete, you call your function's completion handler. If you want to wait until all the queries complete you should set up a GCD DispatchGroup
to synchronize all of those calls.
I just created a question and answer explaining DispatchGroup
s and providing a simple example using them.
Appling that approach to your code might look like the below (see all the MARK: New code
marks in the code for the changes.)
public func fetchSampleData(
sampleInfo: [(String, HKQuantityType?, String)],
completion: @escaping ( _ samples:
Dictionary<String, Dictionary<Date, Double>>) -> Void
) {
let numberOfDataTypes = sampleInfo.count
var resultsDict: [String: [Date: Double]] = [:]
// -------------------------------
// MARK: New code
let dispatchGroup = DispatchGroup()
// Create a DispatchWorkItem to call our completion handler once all our tasks have finished.
let workItem = DispatchWorkItem() {
completion(resultsDict)
}
// -------------------------------
for i in 0..<numberOfDataTypes {
let dataTypeName = sampleInfo[i].0
let sampleType = sampleInfo[i].1!
let unitString = sampleInfo[i].2
var dataTypeDictionary = [Date:Double]()
// Predicate for specifying start and end dates for the query
let predicate = HKQuery
.predicateForSamples(
withStart: Date.distantPast,
end: Date.now,
options: .strictEndDate)
// Set sorting by date.
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierStartDate,
ascending: false)
// Create the query
// MARK: New code
dispatchGroup.enter() // Tell the dispatch group we have added another async task
let query = HKSampleQuery(
sampleType: sampleType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: [sortDescriptor]) { (_, results, error) in
guard error == nil else {
print("Error: \(error!.localizedDescription)")
return
}
print("~~- \(results?.count) \(dataTypeName) samples returned")
for sample in results ?? [] {
let data = sample as! HKQuantitySample
let unit = HKUnit(from: unitString)
let sampleVal = data.quantity.doubleValue(for: unit)
let dateStart = data.startDate
dataTypeDictionary[dateStart] = sampleVal
}
resultsDict[dataTypeName] = dataTypeDictionary
// MARK: New code
dispatchGroup.leave() // tell the dispatch group this task has been completed
}
healthStore.execute(query)
}
dispatchGroup.notify(queue: DispatchQueue.main, work: workItem) // MARK: New code
}
}