使用 HealthKit 卡在完成处理程序/调度组
Stuck at completion handlers / dispatch groups with HealthKit
我在 class:
的初始化中调用了一个函数
doLongTask(forPast: 6)
这是我和调度组一起做的游乐场:
//MAKE THE STRUCT
struct Calorie: Identifiable {
private static var idSequence = sequence(first: 1, next: {[=11=] + 1})
var id: Int
var day: Date
var activeCal: CGFloat
var restingCal: CGFloat
var dietaryCal: CGFloat
init?(id: Int, day: Date, activeCal:CGFloat, restingCal:CGFloat, dietaryCal:CGFloat) {
guard let id = Calorie.idSequence.next() else { return nil}
self.id = id
self.day = day
self.activeCal = activeCal
self.restingCal = restingCal
self.dietaryCal = dietaryCal
}
}
//CREATE HEALTHSTORE
let healthStore = HKHealthStore()
//MAKE TEST ARRAY
var testCalorieArray = [Calorie]()
func doLongTask(forPast days: Int) {
print("Enter the function!")
print("---")
func getTempEnergy (for type:HKQuantityType!, unit u:HKUnit!, start fromDate:Date, end endDate:Date, completion: @escaping (Double) -> Void) {
let countQuantityType = type
let predicate = HKQuery.predicateForSamples(withStart: fromDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: countQuantityType!, quantitySamplePredicate: predicate, options: .cumulativeSum) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: u)
}
DispatchQueue.main.async {
print(resultCount)
completion(resultCount)
}
}
healthStore.execute(query)
}
let queue = DispatchQueue(label: "com.WILDFANGmedia.queues.serial")
let group = DispatchGroup()
let now = Calendar.current.startOfDay(for: Date())
//Initialize to test values to see if they get overwritten
var _activeEnergyBurned:CGFloat = 99.9
var _restingEnergyBurned:CGFloat = 99.9
var _dietaryEnergyConsumed:CGFloat = 99.9
//EACH DAY
for day in 0...days {
group.enter()
queue.async(group: group) {
// Start und Enddatum
let fromDate = Calendar.current.date(byAdding: .day, value: -day-1, to: now)!
let endDate = Calendar.current.date(byAdding: .day, value: -day, to: now)!
getTempEnergy(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), start: fromDate, end: endDate, completion: { (activeEnergyBurned) in
print(activeEnergyBurned)
_activeEnergyBurned = CGFloat(activeEnergyBurned)
})
print("End Datum: \(endDate)")
print("Active Cal: \(_activeEnergyBurned)")
print("Day \(day) done")
print("---")
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(_activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
//SHOW WHAT'S IN THE ARRAY
group.notify(queue: queue) {
print("All tasks done")
print(testCalorieArray)
print("---")
}
//AFTER LOOP, GO ON WITH BUSINESS
print("Continue execution immediately")
}
doLongTask(forPast: 6)
print("AFTER THE FUNCTION")
//TEST LOOP TO RUN LONGER
for i in 1...7 {
sleep(arc4random() % 2)
print("Outter Row Task \(i) done")
}
print(testCalorieArray)
它应该做的是进行 HKStatisticsQuery
调用(稍后会有 3 次调用)并将结果写回我的数组。
但是,它会在函数完成之前写入数组,因此不会返回正确的值。我尝试使用调度组,但我卡住了。
getEnergyTemp()
的完成处理程序中的 print(value)
在测试循环完成后打印出正确的值。
我哪里错了?我以为我已经理解了这个原理,但我就是做不到。
主要问题是您在错误的地方调用了 leave
。所以,而不是:
for day in 0...days {
group.enter()
queue.async(group: group) {
...
getTempEnergy(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), start: fromDate, end: endDate, completion: { (activeEnergyBurned) in
print(activeEnergyBurned)
_activeEnergyBurned = CGFloat(activeEnergyBurned)
})
...
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(_activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
请记住 getTempEnergy
闭包是异步调用的(即稍后调用)。您需要将 leave
调用和结果附加 移到 闭包中:
for day in 0...days {
group.enter()
queue.async {
...
getTempEnergy(for: .quantityType(forIdentifier: .activeEnergyBurned), unit: .kilocalorie(), start: fromDate, end: endDate) { (activeEnergyBurned) in
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
}
请注意,这会使旧变量 _activeEnergyBurned
过时,您现在可以将其删除。
无关,但是:
注意我已经删除了 group
引用作为 queue.async
的参数。当您手动调用 enter
和 leave
时,async
的 group
参数现在是多余的。
在处理本身是异步的调用(这里就是这种情况)时,您通常使用 enter
/leave
模式,或者使用 group
参数来 async
当分派的代码是同步的。但不是两者。
请注意 getTempEnergy
中的所有路径都必须调用完成处理程序(否则您的 DispatchGroup
可能永远无法解析)。因此,在 getTempEnergy
中的那个 guard
语句中也必须调用 completion
。
这就引出了为 completion
闭包提供什么价值的问题。一种方法是使 Double
参数可选,并使 return nil
出现错误。或者,更可靠的方法是使用 Result
type:
func getTempEnergy (for type: HKQuantityType?, unit u: HKUnit, start fromDate: Date, end endDate: Date, completion: @escaping (Result<Double, Error>) -> Void) {
guard let countQuantityType = type else {
completion(.failure(HKProjectError.invalidType))
return
}
let predicate = HKQuery.predicateForSamples(withStart: fromDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: countQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, error in
DispatchQueue.main.async {
guard error == nil else {
completion(.failure(error!))
return
}
guard let resultCount = result?.sumQuantity()?.doubleValue(for: u) else {
completion(.failure(HKProjectError.noValue))
return
}
completion(.success(resultCount))
}
}
healthStore.execute(query)
}
然后
for day in 0...days {
group.enter()
queue.async {
let fromDate = Calendar.current.date(byAdding: .day, value: -day-1, to: now)!
let endDate = Calendar.current.date(byAdding: .day, value: -day, to: now)!
getTempEnergy(for: .quantityType(forIdentifier: .activeEnergyBurned), unit: .kilocalorie(), start: fromDate, end: endDate) { result in
switch result {
case .failure(let error):
print(error)
case .success(let activeEnergyBurned):
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
}
group.leave()
}
}
}
您的自定义错误所在位置:
enum HKProjectError: Error {
case noValue
case invalidType
}
你关心你的数组内容是否有序吗?请注意,这些异步方法可能不会按照您启动它们的相同顺序完成。如果顺序很重要,您可能希望将结果保存在字典中,然后通过数字 day
作为索引检索值。所以,也许用
替换你的数组
var calorieResults = [Int: Calorie]()
然后,在保存结果时:
calorieResults[day] = Calorie(...)
然后,完成后,像这样检索结果:
group.notify(queue: queue) {
print("All tasks done")
for day in 0...days {
print(day, calorieResults[day] ?? "No data found")
}
print("---")
}
这还有一个好处,就是如果一天或多天失败了,您就会知道是哪几天失败了。例如,如果您只有和数组,并且您获得了上周 7 天中的 5 天的数据,您将不知道丢失了哪些天。但是通过为您的模型使用字典,您现在知道哪些天有数据,哪些天没有。
避免使用 sleep
。这会阻塞当前线程。如果您想定期检查发生了什么,请改用计时器。
我在 class:
的初始化中调用了一个函数doLongTask(forPast: 6)
这是我和调度组一起做的游乐场:
//MAKE THE STRUCT
struct Calorie: Identifiable {
private static var idSequence = sequence(first: 1, next: {[=11=] + 1})
var id: Int
var day: Date
var activeCal: CGFloat
var restingCal: CGFloat
var dietaryCal: CGFloat
init?(id: Int, day: Date, activeCal:CGFloat, restingCal:CGFloat, dietaryCal:CGFloat) {
guard let id = Calorie.idSequence.next() else { return nil}
self.id = id
self.day = day
self.activeCal = activeCal
self.restingCal = restingCal
self.dietaryCal = dietaryCal
}
}
//CREATE HEALTHSTORE
let healthStore = HKHealthStore()
//MAKE TEST ARRAY
var testCalorieArray = [Calorie]()
func doLongTask(forPast days: Int) {
print("Enter the function!")
print("---")
func getTempEnergy (for type:HKQuantityType!, unit u:HKUnit!, start fromDate:Date, end endDate:Date, completion: @escaping (Double) -> Void) {
let countQuantityType = type
let predicate = HKQuery.predicateForSamples(withStart: fromDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: countQuantityType!, quantitySamplePredicate: predicate, options: .cumulativeSum) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: u)
}
DispatchQueue.main.async {
print(resultCount)
completion(resultCount)
}
}
healthStore.execute(query)
}
let queue = DispatchQueue(label: "com.WILDFANGmedia.queues.serial")
let group = DispatchGroup()
let now = Calendar.current.startOfDay(for: Date())
//Initialize to test values to see if they get overwritten
var _activeEnergyBurned:CGFloat = 99.9
var _restingEnergyBurned:CGFloat = 99.9
var _dietaryEnergyConsumed:CGFloat = 99.9
//EACH DAY
for day in 0...days {
group.enter()
queue.async(group: group) {
// Start und Enddatum
let fromDate = Calendar.current.date(byAdding: .day, value: -day-1, to: now)!
let endDate = Calendar.current.date(byAdding: .day, value: -day, to: now)!
getTempEnergy(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), start: fromDate, end: endDate, completion: { (activeEnergyBurned) in
print(activeEnergyBurned)
_activeEnergyBurned = CGFloat(activeEnergyBurned)
})
print("End Datum: \(endDate)")
print("Active Cal: \(_activeEnergyBurned)")
print("Day \(day) done")
print("---")
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(_activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
//SHOW WHAT'S IN THE ARRAY
group.notify(queue: queue) {
print("All tasks done")
print(testCalorieArray)
print("---")
}
//AFTER LOOP, GO ON WITH BUSINESS
print("Continue execution immediately")
}
doLongTask(forPast: 6)
print("AFTER THE FUNCTION")
//TEST LOOP TO RUN LONGER
for i in 1...7 {
sleep(arc4random() % 2)
print("Outter Row Task \(i) done")
}
print(testCalorieArray)
它应该做的是进行 HKStatisticsQuery
调用(稍后会有 3 次调用)并将结果写回我的数组。
但是,它会在函数完成之前写入数组,因此不会返回正确的值。我尝试使用调度组,但我卡住了。
getEnergyTemp()
的完成处理程序中的 print(value)
在测试循环完成后打印出正确的值。
我哪里错了?我以为我已经理解了这个原理,但我就是做不到。
主要问题是您在错误的地方调用了 leave
。所以,而不是:
for day in 0...days {
group.enter()
queue.async(group: group) {
...
getTempEnergy(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), start: fromDate, end: endDate, completion: { (activeEnergyBurned) in
print(activeEnergyBurned)
_activeEnergyBurned = CGFloat(activeEnergyBurned)
})
...
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(_activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
请记住 getTempEnergy
闭包是异步调用的(即稍后调用)。您需要将 leave
调用和结果附加 移到 闭包中:
for day in 0...days {
group.enter()
queue.async {
...
getTempEnergy(for: .quantityType(forIdentifier: .activeEnergyBurned), unit: .kilocalorie(), start: fromDate, end: endDate) { (activeEnergyBurned) in
testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!)
group.leave()
}
}
}
请注意,这会使旧变量 _activeEnergyBurned
过时,您现在可以将其删除。
无关,但是:
注意我已经删除了
group
引用作为queue.async
的参数。当您手动调用enter
和leave
时,async
的group
参数现在是多余的。在处理本身是异步的调用(这里就是这种情况)时,您通常使用
enter
/leave
模式,或者使用group
参数来async
当分派的代码是同步的。但不是两者。请注意
getTempEnergy
中的所有路径都必须调用完成处理程序(否则您的DispatchGroup
可能永远无法解析)。因此,在getTempEnergy
中的那个guard
语句中也必须调用completion
。这就引出了为
completion
闭包提供什么价值的问题。一种方法是使Double
参数可选,并使 returnnil
出现错误。或者,更可靠的方法是使用Result
type:func getTempEnergy (for type: HKQuantityType?, unit u: HKUnit, start fromDate: Date, end endDate: Date, completion: @escaping (Result<Double, Error>) -> Void) { guard let countQuantityType = type else { completion(.failure(HKProjectError.invalidType)) return } let predicate = HKQuery.predicateForSamples(withStart: fromDate, end: endDate, options: .strictStartDate) let query = HKStatisticsQuery(quantityType: countQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, error in DispatchQueue.main.async { guard error == nil else { completion(.failure(error!)) return } guard let resultCount = result?.sumQuantity()?.doubleValue(for: u) else { completion(.failure(HKProjectError.noValue)) return } completion(.success(resultCount)) } } healthStore.execute(query) }
然后
for day in 0...days { group.enter() queue.async { let fromDate = Calendar.current.date(byAdding: .day, value: -day-1, to: now)! let endDate = Calendar.current.date(byAdding: .day, value: -day, to: now)! getTempEnergy(for: .quantityType(forIdentifier: .activeEnergyBurned), unit: .kilocalorie(), start: fromDate, end: endDate) { result in switch result { case .failure(let error): print(error) case .success(let activeEnergyBurned): testCalorieArray.append(Calorie(id: 1, day: endDate, activeCal: CGFloat(activeEnergyBurned), restingCal: CGFloat(_restingEnergyBurned), dietaryCal: CGFloat(_dietaryEnergyConsumed))!) } group.leave() } } }
您的自定义错误所在位置:
enum HKProjectError: Error { case noValue case invalidType }
你关心你的数组内容是否有序吗?请注意,这些异步方法可能不会按照您启动它们的相同顺序完成。如果顺序很重要,您可能希望将结果保存在字典中,然后通过数字
替换你的数组day
作为索引检索值。所以,也许用var calorieResults = [Int: Calorie]()
然后,在保存结果时:
calorieResults[day] = Calorie(...)
然后,完成后,像这样检索结果:
group.notify(queue: queue) { print("All tasks done") for day in 0...days { print(day, calorieResults[day] ?? "No data found") } print("---") }
这还有一个好处,就是如果一天或多天失败了,您就会知道是哪几天失败了。例如,如果您只有和数组,并且您获得了上周 7 天中的 5 天的数据,您将不知道丢失了哪些天。但是通过为您的模型使用字典,您现在知道哪些天有数据,哪些天没有。
避免使用
sleep
。这会阻塞当前线程。如果您想定期检查发生了什么,请改用计时器。