CombineとSwiftUIを使用して非同期操作を処理する方法を理解しようとしています。
たとえば、私はHealthKitManager
クラスを持っています。これは、とりわけ、ヘルスストアの承認の要求を処理します…
_final class HealthKitManager {
enum Error: Swift.Error {
case notAvailable
case authorisationError(Swift.Error)
}
let healthStore = HKHealthStore()
func getHealthKitData(for objects: Set<HKObjectType>, completion: @escaping (Result<Bool, Error>) -> Void) {
guard HKHealthStore.isHealthDataAvailable() else {
completion(.failure(.notAvailable))
return
}
self.healthStore.requestAuthorization(toShare: nil, read: objects) { completed, error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(.authorisationError(error)))
}
completion(.success(completed))
}
}
}
}
_
これは次のように使用されます…
_struct ContentView: View {
let healthKitManager = HealthKitManager()
@State var showNextView = false
@State var showError = false
@State var hkError: Error?
let objectTypes = Set([HKObjectType.quantityType(forIdentifier: .bloodGlucose)!])
var body: some View {
NavigationView {
NavigationLink(destination: NextView(), isActive: $showNextView) {
Button("Show Next View") {
self.getHealthKitData()
}
}.navigationBarTitle("Content View")
}.alert(isPresented: $showError) {
Alert(title: Text("Error"), message: Text(hkError?.localizedDescription ?? ""), dismissButton: .cancel())
}
}
func getHealthKitData() {
self.healthKitManager.getHealthKitData(for: self.objectTypes) { result in
switch result {
case let .success(complete):
self.showNextView = complete
case let .failure(error):
self.hkError = error
self.showError = true
}
}
}
}
_
私がやりたいことは、Result
クロージャーではなくCombineを使用することです。私はこのようなものを推測しています...
_final class HealthKitManager: ObservableObject {
enum Error: Swift.Error {
case notAvailable
case authorisationError(Swift.Error)
}
@Published var authorisationResult: Result<Bool, Error>?
let healthStore = HKHealthStore()
func getHealthKitData(for objects: Set<HKObjectType>) {
guard HKHealthStore.isHealthDataAvailable() else {
self.authorisationResult = .failure(.notAvailable)
return
}
self.healthStore.requestAuthorization(toShare: nil, read: objects) { completed, error in
DispatchQueue.main.async {
if let error = error {
self.authorisationResult = .failure(.authorisationError(error))
return
}
self.authorisationResult = .success(completed)
}
}
}
}
_
しかし、NavigationLink(isActive:)
およびalert(isPresented:)
の値にバインドする方法と、エラーを取得する方法は不明です。
_struct ContentView: View {
@ObservedObject var healthKitManager = HealthKitManager()
let objectTypes = Set([HKObjectType.quantityType(forIdentifier: .bloodGlucose)!])
var body: some View {
NavigationView {
NavigationLink(destination: NextView(), isActive: ????) { // How do I get this
Button("Show Next View") {
self.healthKitManager.getHealthKitData(for: self.objectTypes)
}
}.navigationBarTitle("Content View")
}.alert(isPresented: ????) { // or this
Alert(title: Text("Error"), message: Text(????.localizedDescription ?? ""), dismissButton: .cancel()) // or this
}
}
}
_
_@Published var authorisationResult: Result<Bool, Error>?
_が正しくないと思いますか? _Future / Promise
_を使用する必要がありますか?
更新
警告を表示する別の方法があることを発見しました…
_.alert(item: self.$error) { error in
Alert(title: Text(error.localizedDescription))
_
つまり、showError
にBoolは必要ありません(Error
オブジェクトがIdentifiable
である必要があるだけです)
extension Result {
func getFailure() -> Failure? {
switch self {
case .failure(let er):
return er
default:
return nil
}
}
func binding<B>(
success successClosure: (@escaping (Success) -> B),
failure failureClosure: @escaping (Failure) -> B) -> Binding<B> {
return Binding<B>(
get: {
switch self {
case .success(let value):
return successClosure(value)
case .failure(let failure):
return failureClosure(failure)
}
}, set: { _ in })
}
func implicitBinding(failure failureClosure: @escaping (Failure) -> Success) -> Binding<Success> {
return binding(success: { $0 }, failure: failureClosure)
}
}
class HealthKitManager: ObservableObject {
enum Error: Swift.Error {
case authorisationError(Swift.Error)
case notAvailable
}
@Published var authorisationResult = Result<Bool, Error>.failure(.notAvailable)
let healthStore = HKHealthStore()
func getHealthKitData(for objects: Set<HKObjectType>) {
guard HKHealthStore.isHealthDataAvailable() else {
self.authorisationResult = .failure(.notAvailable)
return
}
self.healthStore.requestAuthorization(toShare: nil, read: objects) { completed, error in
DispatchQueue.main.async {
if let error = error {
self.authorisationResult = .failure(.authorisationError(error))
return
}
self.authorisationResult = .success(completed)
}
}
}
}
struct ContentView: View {
@ObservedObject var healthKitManager = HealthKitManager()
let objectTypes = Set([HKObjectType.quantityType(forIdentifier: .bloodGlucose)!])
var body: some View {
NavigationView {
NavigationLink(destination: NextView(),
isActive: healthKitManager.authorisationResult.implicitBinding(failure: { _ in false })) {
Button("Show Next View") {
self.healthKitManager.getHealthKitData(for: self.objectTypes)
}
}.navigationBarTitle("Content View")
}.alert(isPresented: healthKitManager.authorisationResult.binding(success: { _ in false }, failure: { _ in true })) {
let message = healthKitManager.authorisationResult.getFailure()?.localizedDescription ?? ""
return Alert(title: Text("Error"), message: Text(message), dismissButton: .cancel()) // or this
}
}
}