hoạt động không đồng bộ bằng cách sử dụng Kết hợp và SwiftUI


8

Tôi đang cố gắng tìm ra cách làm việc với các hoạt động không đồng bộ bằng cách sử dụng Kết hợp và SwiftUI.

Ví dụ: tôi có một HealthKitManagerlớp, trong số những thứ khác, xử lý yêu cầu ủy quyền cửa hàng y tế

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))
            }
        }
    }
}

được sử dụng như sau

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
            }
        }
    }
}

Những gì tôi muốn làm là sử dụng Kết hợp thay vì Resultđóng cửa. Tôi đoán điều gì đó như thế này

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)
            }
        }
    }
}

Nhưng sau đó, không rõ làm thế nào để liên kết với các giá trị cho NavigationLink(isActive:)alert(isPresented:), và nhận được lỗi.

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
        }
    }
}

Tôi đoán điều đó @Published var authorisationResult: Result<Bool, Error>?không đúng? Tôi có nên sử dụng Future / Promise, cái gì khác?


Cập nhật

Tôi thấy có một cách khác để trình bày một cảnh báo

.alert(item: self.$error) { error in
        Alert(title: Text(error.localizedDescription))

điều đó có nghĩa là tôi không cần Bool cho showError(nó chỉ yêu cầu Errorđối tượng là Identifiable)


@Publishedcung cấp cho bạn một nhà xuất bản và tích hợp tự động với làm mới chế độ xem SwiftUI thông qua thuộc @ObservedObjecttính động. Bạn có thể sử dụng bất cứ thứ gì, nhưng hãy nghĩ về ưu và nhược điểm . Đó có phải là mục tiêu để làm cho những điều đơn giản trở nên phức tạp?
Asperi

Câu trả lời:


4

Tôi thích có resultnhư bạn đã làm trong biến thể thứ hai

@Published var authorisationResult: Result<Bool, Error>?

vì vậy cách tiếp cận có thể cho việc sử dụng có thể như sau

NavigationLink(destination: NextView(), isActive: 
         Binding<Bool>.ifSuccess(self.healthKitManager.authorisationResult)) {
    Button("Show Next View") {
        self.healthKitManager.getHealthKitData(for: self.objectTypes)
    }
}.navigationBarTitle("Content View")

nơi một số mở rộng thuận tiện

extension Binding {
    static func ifSuccess<E>(_ result: Result<Bool, E>?) -> Binding<Bool> where E: Error {
        Binding<Bool>(
            get: {
                guard let result = result else { return false }
                switch result {
                 case .success(true):
                    return true
                 default:
                    return false
            }
        }, set: { _ in })
    }
}

các biến thể cho errorcó thể được thực hiện theo cách tương tự.


Cảm ơn câu trả lời của bạn - thật xấu hổ vì điều này đòi hỏi mã bổ sung như vậy để làm điều này.
Ashley Mills

4
@AshleyMills, nếu Apple cung cấp API cho mọi thứ, chúng ta sẽ làm gì? Chúng ta không phải là lập trình viên sao? = ^)
Asperi

3

Sửa đổi câu trả lời của tôi dựa trên câu trả lời của @ Asperi's :

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
        }
    }
}

1
Cảm ơn bạn. Điều đó sẽ chắc chắn làm việc, nhưng có giá trị riêng biệt cho hasAuthorizationError, authorizationErrorisAuthorizedkhông có vẻ đúng bằng cách nào đó ... đặc biệt là khi cả 3 được bao phủ bởi các loại quả duy nhất. Ngoài ra lớp này có thể được sử dụng cho các hoạt động không đồng bộ khác, vì vậy việc thêm 3 @Publishedvars bổ sung cho mỗi op có vẻ rất nhiều. Tôi đã hy vọng Combine sẽ có cách xử lý việc này tốt hơn.
Ashley Mills
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.