当receiveCompletion错误时,订阅将取消 [英] subscription cancels when receiveCompletion with error

查看:63
本文介绍了当receiveCompletion错误时,订阅将取消的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在以MVVM模式设计的应用程序中,我有一个loginView.如果存在网络或身份验证问题,登录可能会失败.我的目标是捕获错误并显示相应的警报.我定义并枚举了警报,如下所示:

In my app which is designed in MVVM-pattern I have a loginView. Login may fail if there is either a network or authentication problem. My goal is to catch errors and display the corresponding alerts. I defined and enum for alerts as follow:

enum Alerts: Identifiable {
    var id: Int {
        return self.hashValue
    }

    case networkError
    case authenticationError
}

该视图的实现是:

struct LoginView: View {

    @ObservedObject var viewModel = LoginViewModel()

    var body: some View {
    
        VStack {
            TextField("Enter e-mail address", text: $viewModel.email)
            SecureField("Enter password", text: $viewModel.password)
        
            Button("Log In") {
                viewModel.login()
            }
        }.alert(item: $viewModel.errorAlert, content: { alert in
            switch alert {
            case .networkError:
                return Alert(title: Text("Error"), message: Text("Check internet Connection"), dismissButton: .default(Text("Ok")))
            case .authenticationError:
                return Alert(title: Text("Error"), message: Text("Some error occured, please try again"), dismissButton: .default(Text("Ok")))
            }
        })
     }
 }

和viewModel是:

and the viewModel is:

class LoginViewModel: ObservableObject {

    @Published var email: String = ""
    @Published var password: String = ""

    @Published var errorAlert: Alerts? = nil
    @Published var token: Token? = nil

    var authentication = PassthroughSubject<User, WebserviceError>()

    var cancellables = Set<AnyCancellable>()

    init() {
    
        authentication.map { Webservice().authenticate($0) }.switchToLatest().print().sink { error in
            self.errorAlert = Alerts.networkError
        } receiveValue: { token in
            self.token = token
        }.store(in: &cancellables)

        token.map { KeychainWrapper.save(token: $0)}?.sink(receiveCompletion: { error in
            self.errorAlert = Alerts.authenticationError
        }, receiveValue: { _ in
            //
        }).store(in: &cancellables)
    }

    func login() {
        authentication.send(User(username: email, password: password))
    }
}

Web服务的实现

class Webservice {
    func authenticate(_ user: User) -> AnyPublisher<Token, WebserviceError> {
        return Future<Token, WebserviceError> { promis in
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                if user.username.lowercased() == "root" && user.password == "1234" {
                    promis(.success(Token(access: "asdasda", refresh: "sdfsdfsdf", exprationDate: Date().addingTimeInterval(120))))
                } else {
                    promis(.failure(.unknown))
                }
            }
        }.eraseToAnyPublisher()
    }
}

请考虑以下情形:用户首次运行该应用.她/他在第一次尝试中输入了错误的用户名/密码.

considering the following scenario: user runs the app for the first time. in the first try she/he puts the wrong username/password.

发生的情况是,将向用户显示等效的警报,并且可以单击确定"将其关闭.问题是从第二次开始,单击登录按钮后,什么也没发生.好像Viewmodel中的authentication.map {....}被永久取消了.为什么会这样?

what happens is that the user will be shown the equivalent alert and can click ok to dismiss it. The problem is that from the 2nd time so on, after clicking the login button, nothing happen. looks like the authentication.map { .... } in Viewmodel is cancelled forever. why is that so?

推荐答案

发布者一旦失败就会停止发出值的预期行为.

It is expected behaviour for a publisher to stop emitting values once it fails.

您可以通过发送登录功能的身份验证请求来解决此问题.如果发布者失败,您将向用户显示错误,他们将能够更改其输入并再次点击登录.这将导致发出新请求:

You could approach this by sending a request to authenticate from the login function. If the publisher fails you will display an error to the user and they will be able to change their input and tap login again. This will result in new request being made:

class LoginViewModel: ObservableObject {

    @Published var email: String = ""
    @Published var password: String = ""

    @Published private (set) var errorAlert: Alerts? = nil
    @Published private (set) var token: Token? = nil

    private let webservice: Webservice
    private var cancellables = Set<AnyCancellable>()

    init(webservice: Webservice) {
        self.webservice = webservice
        
        $token
            .sink { token in
                KeychainWrapper.save(token: token)
            }
            .store(in: &cancellables)
    }

    func login() {
        let user = User(username: email, password: password)

        webservice
            .authenticate(user)
            .sink(receiveCompletion: { [weak self] completion in
                if completion == .failure(_) {
                    self?.errorAlert = .authenticationError
                }
            }, receiveValue: { [weak self] token in
                self?.token = token
            })
            .store(in: &cancellables)
    }
}

这篇关于当receiveCompletion错误时,订阅将取消的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆