У меня есть следующая проблема. У меня есть два метода, которые возвращают 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 { $0.element } // if the either request succeeds
.bind(to: _token) // notify the VC of the token
.disposed(by: disposeBag)
tokenResult
.compactMap { $0.error } // if both requests fail
.bind(to: _loadingErrorOccurred) // notify the VC of the error
.disposed(by: disposeBag)
}
}
Я также хотел бы сделать одно дополнение. Для второй привязки tokenResult
я добавил .skip(1)
после compactMap
. Я только хотел отобразить сообщение об ошибке, когда обе попытки терпят неудачу.
Какую версию RxSwift вы используете? В моем перечислении SingleEvent есть успех и ошибка, а не успех и неудача. Я ожидаю, что они одинаковы.
Я использую версию 6.0.0
Это было именно то, что я искал! Остался один вопрос, в
authService
, где вы возвращаетеSingle
, у вас есть этот код:observer(.error(error ?? RxError.unknown))
. Однако для меня я могу использовать только.success
и.failure
. Есть ли разница между неудачей и ошибкой?