Estoy tratando de descubrir cómo trabajar con operaciones asíncronas usando Combine y SwiftUI.
Por ejemplo, tengo una HealthKitManagerclase que, entre otras cosas, maneja la solicitud de autorización de tienda de salud ...
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))
}
}
}
}
que se usa de la siguiente manera ...
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
}
}
}
}
Lo que me gustaría hacer es usar Combinar en lugar de un Resultcierre. Estoy adivinando algo como esto ...
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)
}
}
}
}
Pero entonces no está claro cómo vincular los valores para NavigationLink(isActive:)y alert(isPresented:), y obtener el error.
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
}
}
}
Supongo que eso @Published var authorisationResult: Result<Bool, Error>?no es correcto? ¿Debería estar usando Future / Promisealgo más?
Actualizar
Descubrí que hay otra forma de presentar una alerta ...
.alert(item: self.$error) { error in
Alert(title: Text(error.localizedDescription))
lo que significa que no necesito el Bool para showError(solo requiere que el Errorobjeto sea Identifiable)
fuente

@Publishedle proporciona un editor y tiene integración automática con la actualización de vista SwiftUI a través de@ObservedObjectla propiedad dinámica. Puedes usar cualquier cosa, pero piensa en pros y contras . ¿Es el objetivo hacer que las cosas simples sean complejas?Respuestas:
Me gusta tener
resultcomo lo hiciste en la segunda varianteentonces el posible enfoque para el uso puede ser el siguiente
donde alguna extensión conveniente
la variante para
errorse puede hacer de manera similar.fuente
Revisé mi respuesta para que se base en la respuesta de @ Asperi :
fuente
hasAuthorizationError,authorizationErroryisAuthorizedno parece correcto de alguna manera ... especialmente porque los 3 están cubiertos por el tipo de Resultado único. Además, esta clase se puede usar para otras operaciones asíncronas, por lo que agregar 3@Publishedvars adicionales para cada operación parece mucho. Esperaba que Combine tuviera una mejor manera de manejar esto.