RxSwift 在错误事件上调用一个方法或者根本不调用它
RxSwift call a method on error event or don't call it at all
我有以下问题。我有两种方法都 return Single<String>
。我想在开始时调用第一个(例如用户操作),如果我收到 .success
事件,我想初始化导航。但是,当发生错误时,我想先调用另一个方法进行显示,然后显示错误或根据其结果进行导航。
具体例子:
- 我启动了一个视图控制器并触发了一个登录功能。
- 如果静默登录无法获取令牌(获取错误事件),我希望向用户显示登录掩码(交互式令牌请求)。
- 如果静默登录有效,我不想显示登录掩码,直接开始导航。
代码如下:
import UIKit
import RxSwift
import RxCocoa
final class SignInViewModel {
private let authService: AuthenticationService
private let disposeBag = DisposeBag()
let loadingErrorOccurred = PublishSubject<Void>()
init(authService: AuthenticationService) {
self.authService = authService
}
func signInUser(in viewController: UIViewController) {
// I want to trigger the getTokenInteractively when the getTokenSilently() fails
// and never trigger it if it succeeds.
getTokenSilently()
.asObservable()
.take(1)
.catchAndReturn("")
.map { token in !token.isEmpty }
.bind(to: wasSilentTokenRequestSuccessful)
.disposed(by: disposeBag)
}
private func getTokenSilently() -> Single<String> {
return Single.create { [authService] observer -> Disposable in
authService.getTokenSilently { token, error in
if let token = token, error != nil {
observer(.success(token))
} else {
observer(.failure(error ?? UnknownError()))
}
}
return Disposables.create()
}
}
private func getTokenInteractively(viewController: UIViewController) -> Single<String> {
return Single.create { [authService] observer -> Disposable in
authService.getTokenInteractively(parentView: viewController) { token, error in
if let token = token, error != nil {
observer(.success(token))
} else {
observer(.failure(error ?? UnknownError()))
}
}
return Disposables.create()
}
}
}
我正在寻找实现预期结果的正确方法。我想到了某种首先触发一个函数的运算符,只有当结果失败时才会触发下一个函数。然后流的其余部分可以保持不变。
在这种情况下需要知道的一个有用的运算符是 catchError()
,它将允许您用其他一些可观察的替换错误事件。
首先,我建议您将您的两个网络调用移至它们所属的身份验证服务中。
extension AuthenticationService {
func rx_getTokenSilently() -> Single<String> {
Single.create { observer in
getTokenSilently { (result, error) in
if let result = result {
observer(.success(result))
}
else {
observer(.error(error ?? RxError.unknown))
}
}
return Disposables.create()
}
}
func rx_getTokenInteractively(parentView: UIViewController) -> Single<String> {
Single.create { observer in
getTokenInteractively(parentView: parentView) { (result, error) in
if let result = result {
observer(.success(result))
}
else {
observer(.error(error ?? RxError.unknown))
}
}
return Disposables.create()
}
}
}
现在您的 SignInViewModel 看起来像这样:
final class SignInViewModel {
private let authService: AuthenticationService
private let disposeBag = DisposeBag()
private let _token = PublishSubject<String>()
private let _loadingErrorOccurred = PublishSubject<Error>()
// your view controller can subscribe to these two in order to do its navigation.
let token: Observable<String>
let loadingErrorOccurred: Observable<Error>
init(authService: AuthenticationService) {
self.authService = authService
token = _token.asObservable()
loadingErrorOccurred = _loadingErrorOccurred.asObservable()
}
func signInUser(in viewController: UIViewController) {
let tokenResult = authService.rx_getTokenSilently() // make first request
.catchError { [authService] _ in
// if first request fails, make second request
authService.rx_getTokenInteractively(parentView: viewController)
}
.asObservable()
.materialize()
tokenResult.compactMap { [=11=].element } // if the either request succeeds
.bind(to: _token) // notify the VC of the token
.disposed(by: disposeBag)
tokenResult
.compactMap { [=11=].error } // if both requests fail
.bind(to: _loadingErrorOccurred) // notify the VC of the error
.disposed(by: disposeBag)
}
}
我有以下问题。我有两种方法都 return Single<String>
。我想在开始时调用第一个(例如用户操作),如果我收到 .success
事件,我想初始化导航。但是,当发生错误时,我想先调用另一个方法进行显示,然后显示错误或根据其结果进行导航。
具体例子:
- 我启动了一个视图控制器并触发了一个登录功能。
- 如果静默登录无法获取令牌(获取错误事件),我希望向用户显示登录掩码(交互式令牌请求)。
- 如果静默登录有效,我不想显示登录掩码,直接开始导航。
代码如下:
import UIKit
import RxSwift
import RxCocoa
final class SignInViewModel {
private let authService: AuthenticationService
private let disposeBag = DisposeBag()
let loadingErrorOccurred = PublishSubject<Void>()
init(authService: AuthenticationService) {
self.authService = authService
}
func signInUser(in viewController: UIViewController) {
// I want to trigger the getTokenInteractively when the getTokenSilently() fails
// and never trigger it if it succeeds.
getTokenSilently()
.asObservable()
.take(1)
.catchAndReturn("")
.map { token in !token.isEmpty }
.bind(to: wasSilentTokenRequestSuccessful)
.disposed(by: disposeBag)
}
private func getTokenSilently() -> Single<String> {
return Single.create { [authService] observer -> Disposable in
authService.getTokenSilently { token, error in
if let token = token, error != nil {
observer(.success(token))
} else {
observer(.failure(error ?? UnknownError()))
}
}
return Disposables.create()
}
}
private func getTokenInteractively(viewController: UIViewController) -> Single<String> {
return Single.create { [authService] observer -> Disposable in
authService.getTokenInteractively(parentView: viewController) { token, error in
if let token = token, error != nil {
observer(.success(token))
} else {
observer(.failure(error ?? UnknownError()))
}
}
return Disposables.create()
}
}
}
我正在寻找实现预期结果的正确方法。我想到了某种首先触发一个函数的运算符,只有当结果失败时才会触发下一个函数。然后流的其余部分可以保持不变。
在这种情况下需要知道的一个有用的运算符是 catchError()
,它将允许您用其他一些可观察的替换错误事件。
首先,我建议您将您的两个网络调用移至它们所属的身份验证服务中。
extension AuthenticationService {
func rx_getTokenSilently() -> Single<String> {
Single.create { observer in
getTokenSilently { (result, error) in
if let result = result {
observer(.success(result))
}
else {
observer(.error(error ?? RxError.unknown))
}
}
return Disposables.create()
}
}
func rx_getTokenInteractively(parentView: UIViewController) -> Single<String> {
Single.create { observer in
getTokenInteractively(parentView: parentView) { (result, error) in
if let result = result {
observer(.success(result))
}
else {
observer(.error(error ?? RxError.unknown))
}
}
return Disposables.create()
}
}
}
现在您的 SignInViewModel 看起来像这样:
final class SignInViewModel {
private let authService: AuthenticationService
private let disposeBag = DisposeBag()
private let _token = PublishSubject<String>()
private let _loadingErrorOccurred = PublishSubject<Error>()
// your view controller can subscribe to these two in order to do its navigation.
let token: Observable<String>
let loadingErrorOccurred: Observable<Error>
init(authService: AuthenticationService) {
self.authService = authService
token = _token.asObservable()
loadingErrorOccurred = _loadingErrorOccurred.asObservable()
}
func signInUser(in viewController: UIViewController) {
let tokenResult = authService.rx_getTokenSilently() // make first request
.catchError { [authService] _ in
// if first request fails, make second request
authService.rx_getTokenInteractively(parentView: viewController)
}
.asObservable()
.materialize()
tokenResult.compactMap { [=11=].element } // if the either request succeeds
.bind(to: _token) // notify the VC of the token
.disposed(by: disposeBag)
tokenResult
.compactMap { [=11=].error } // if both requests fail
.bind(to: _loadingErrorOccurred) // notify the VC of the error
.disposed(by: disposeBag)
}
}