当receiveCompletion错误时,订阅将取消 [英] subscription cancels when receiveCompletion with error
问题描述
在以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屋!