Я экспериментировал с некоторыми новыми быстрыми архитектурами и шаблонами и заметил странную проблему с RxSwift, когда кажется, что я выполняю вызов службы и происходит ошибка - например, пользователь вводит неверный пароль - тогда он, похоже, удаляет мои подписки, поэтому я не могу сделать сервисный вызов снова
Я не уверен, почему это происходит. Я сделал небольшой мини-проект, демонстрирующий проблему с примером приложения для входа.
Моя ViewModel выглядит так
import RxSwift
import RxCocoa
import RxCoordinator
import RxOptional
extension LoginModel : ViewModelType {
struct Input {
let loginTap : Observable<Void>
let password : Observable<String>
}
struct Output {
let validationPassed : Driver<Bool>
let loginActivity : Driver<Bool>
let loginServiceError : Driver<Error>
let loginTransitionState : Observable<TransitionObservables>
}
func transform(input: LoginModel.Input) -> LoginModel.Output {
// check if email passes regex
let isValid = input.password.map{(val) -> Bool in
UtilityMethods.isValidPassword(password: val)
}
// handle response
let loginResponse = input.loginTap.withLatestFrom(input.password).flatMapLatest { password in
return self.service.login(email: self.email, password: password)
}.share()
// handle loading
let loginServiceStarted = input.loginTap.map{true}
let loginServiceStopped = loginResponse.map{_ in false}
let resendActivity = Observable.merge(loginServiceStarted, loginServiceStopped).materialize().map{$0.element}.filterNil()
// handle any errors from service call
let serviceError = loginResponse.materialize().map{$0.error}.asDriver(onErrorJustReturn: RxError.unknown).filterNil()
let loginState = loginResponse.map { _ in
return self.coordinator.transition(to: .verifyEmailController(email : self.email))
}
return Output(validationPassed : isValid.asDriver(onErrorJustReturn: false), loginActivity: resendActivity.asDriver(onErrorJustReturn: false), loginServiceError: serviceError, loginTransitionState : loginState)
}
}
class LoginModel {
private let coordinator: AnyCoordinator<WalkthroughRoute>
let service : LoginService
let email : String
init(coordinator : AnyCoordinator<WalkthroughRoute>, service : LoginService, email : String) {
self.service = service
self.email = email
self.coordinator = coordinator
}
}
А мой ViewController выглядит так
import UIKit
import RxSwift
import RxCocoa
class TestController: UIViewController, WalkthroughModuleController, ViewType {
// password
@IBOutlet var passwordField : UITextField!
// login button
@IBOutlet var loginButton : UIButton!
// disposes of observables
let disposeBag = DisposeBag()
// view model to be injected
var viewModel : LoginModel!
// loader shown when request is being made
var generalLoader : GeneralLoaderView?
override func viewDidLoad() {
super.viewDidLoad()
}
// bindViewModel is called from route class
func bindViewModel() {
let input = LoginModel.Input(loginTap: loginButton.rx.tap.asObservable(), password: passwordField.rx.text.orEmpty.asObservable())
// transforms input into output
let output = transform(input: input)
// fetch activity
let activity = output.loginActivity
// enable/disable button based on validation
output.validationPassed.drive(loginButton.rx.isEnabled).disposed(by: disposeBag)
// on load
activity.filter{$0}.drive(onNext: { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.generalLoader = UtilityMethods.showGeneralLoader(container: strongSelf.view, message: .Loading)
}).disposed(by: disposeBag)
// on finish loading
activity.filter{!$0}.drive(onNext : { [weak self] _ in
guard let strongSelf = self else { return }
UtilityMethods.removeGeneralLoader(generalLoader: strongSelf.generalLoader)
}).disposed(by: disposeBag)
// if any error occurs
output.loginServiceError.drive(onNext: { [weak self] errors in
guard let strongSelf = self else { return }
UtilityMethods.removeGeneralLoader(generalLoader: strongSelf.generalLoader)
print(errors)
}).disposed(by: disposeBag)
// login successful
output.loginTransitionState.subscribe().disposed(by: disposeBag)
}
}
Мой класс обслуживания
import RxSwift
import RxCocoa
struct LoginResponseData : Decodable {
let msg : String?
let code : NSInteger
}
class LoginService: NSObject {
func login(email : String, password : String) -> Observable<LoginResponseData> {
let url = RequestURLs.loginURL
let params = ["email" : email,
"password": password]
print(params)
let request = AFManager.sharedInstance.setupPostDataRequest(url: url, parameters: params)
return request.map{ data in
return try JSONDecoder().decode(LoginResponseData.self, from: data)
}.map{$0}
}
}
Если я введу действительный пароль, запрос будет работать нормально. Если я удаляю код перехода для целей тестирования, я мог бы продолжать вызывать службу входа снова и снова, пока пароль действителен. Но как только возникает какая-либо ошибка, наблюдаемые объекты, относящиеся к служебному вызову, удаляются, поэтому пользователь больше не может пытаться повторить служебный вызов
Пока что я нашел единственный способ исправить это, если возникнет какая-либо ошибка, снова вызвать bindViewModel, чтобы снова настроить подписку. Но это похоже на очень плохую практику.
Любой совет будет высоко ценится!