iOS ViewController 加载异步 (HealthKit) 数据的正确架构
Correct architecture of iOS ViewController loading async (HealthKit) data
下面的代码在 90% 的情况下都能正常工作,但有时我会 return 一个错误(即 self.hideAllStackViewsAndShowNoWorkoutsMessage() 将被调用),即使有要加载的训练。我认为这是一个 VC 生命周期计时问题,但我在我的代码中找不到缺陷?
MyViewController {
override func viewWillAppear(_ animated: Bool) {
loadAndSetWorkout()
}
func loadAndSetWorkout() {
WorkoutManager.loadMostRecentWorkout { (workout, error) in
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
if let unwrappedWorkout = workout {
self.selectedWorkout = unwrappedWorkout
} else {
if let unwrappedError = error {
self.hideAllStackViewsAndShowNoWorkoutsMessage()
print("Error in LastWorkoutTVC loadMostRecentWorkout = \(unwrappedError)")
}
}
}
}
}
}
class func loadMostRecentWorkout(handler: @escaping (HKWorkout?, WorkoutManagerError?) -> Void) {
let workoutPredicate = HKQuery.predicateForWorkouts(with: .other)
let sourcePredicate = HKQuery.predicateForObjects(from: HKSource.default()) //limit query to only this app
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictStartDate)
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate, sourcePredicate, mostRecentPredicate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: HKObjectType.workoutType(), predicate: compound, limit: 1, sortDescriptors: [sortDescriptor]) { (query, samples, error) in
if let unwrappedError = error {
handler(nil, WorkoutManagerError.generalError(unwrappedError.localizedDescription))
return //added this return
}
guard let samples = samples as? [HKWorkout] else {
handler(nil, WorkoutManagerError.generalError("no samples in loadMostRecentWorkout"))
return
}
guard let mostRecentWorkout = samples.first else {
handler(nil, WorkoutManagerError.generalError("no first in samples in loadMostRecentWorkout"))
return
}
handler(mostRecentWorkout, nil)
}
HealthStoreSingleton.sharedInstance.healthStore.execute(query)
}
我把它放在这里只是为了展示另一种设计,因为我无法在评论中添加代码。
完成处理程序相关代码的语义本质上是这样的:
如果至少有一个样本,请准确无误地给出。
否则报错就报错,或者报错没有样本。
您有 "no samples" 错误,所以我们真的不需要 "no first sample" 错误。这是不必要的,所以我们可以摆脱它。
可能的重构可能看起来像这样。
// We either give a sample, or some error.
if let samples = samples as? [HKWorkout], let first = samples.first {
handler(first, nil)
} else {
handler(nil, .generalError(error?.localizedDescription ?? "no samples"))
}
同样的语义表达得更清楚
这不是答案,但由于永远不会为您的处理程序提供样本和错误,而当您知道自己的锻炼数据中有样本时您会收到错误,那么罪魁祸首可能是谓词/查询代码?
下面的代码在 90% 的情况下都能正常工作,但有时我会 return 一个错误(即 self.hideAllStackViewsAndShowNoWorkoutsMessage() 将被调用),即使有要加载的训练。我认为这是一个 VC 生命周期计时问题,但我在我的代码中找不到缺陷?
MyViewController {
override func viewWillAppear(_ animated: Bool) {
loadAndSetWorkout()
}
func loadAndSetWorkout() {
WorkoutManager.loadMostRecentWorkout { (workout, error) in
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
if let unwrappedWorkout = workout {
self.selectedWorkout = unwrappedWorkout
} else {
if let unwrappedError = error {
self.hideAllStackViewsAndShowNoWorkoutsMessage()
print("Error in LastWorkoutTVC loadMostRecentWorkout = \(unwrappedError)")
}
}
}
}
}
}
class func loadMostRecentWorkout(handler: @escaping (HKWorkout?, WorkoutManagerError?) -> Void) {
let workoutPredicate = HKQuery.predicateForWorkouts(with: .other)
let sourcePredicate = HKQuery.predicateForObjects(from: HKSource.default()) //limit query to only this app
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictStartDate)
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate, sourcePredicate, mostRecentPredicate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: HKObjectType.workoutType(), predicate: compound, limit: 1, sortDescriptors: [sortDescriptor]) { (query, samples, error) in
if let unwrappedError = error {
handler(nil, WorkoutManagerError.generalError(unwrappedError.localizedDescription))
return //added this return
}
guard let samples = samples as? [HKWorkout] else {
handler(nil, WorkoutManagerError.generalError("no samples in loadMostRecentWorkout"))
return
}
guard let mostRecentWorkout = samples.first else {
handler(nil, WorkoutManagerError.generalError("no first in samples in loadMostRecentWorkout"))
return
}
handler(mostRecentWorkout, nil)
}
HealthStoreSingleton.sharedInstance.healthStore.execute(query)
}
我把它放在这里只是为了展示另一种设计,因为我无法在评论中添加代码。
完成处理程序相关代码的语义本质上是这样的:
如果至少有一个样本,请准确无误地给出。 否则报错就报错,或者报错没有样本。
您有 "no samples" 错误,所以我们真的不需要 "no first sample" 错误。这是不必要的,所以我们可以摆脱它。
可能的重构可能看起来像这样。
// We either give a sample, or some error.
if let samples = samples as? [HKWorkout], let first = samples.first {
handler(first, nil)
} else {
handler(nil, .generalError(error?.localizedDescription ?? "no samples"))
}
同样的语义表达得更清楚
这不是答案,但由于永远不会为您的处理程序提供样本和错误,而当您知道自己的锻炼数据中有样本时您会收到错误,那么罪魁祸首可能是谓词/查询代码?