У вас есть несколько отдельных битов логики и побочных эффектов, и вы пытаетесь объединить их все в одну плоскую карту. Я предлагаю разбить их на составные части.
Кроме того, ваша логика ошибок неверна. Если ваша сетевая служба выдает ошибку, будет отображаться ваш баннер «К сожалению», но это также разорвет вашу цепочку, и пользователь не сможет выбрать другое местоположение. Мой код ниже решает эту проблему.
Все функции ниже являются свободными функциями. Поскольку они не привязаны к конкретному контроллеру представления, их можно использовать и тестировать независимо. Также обратите внимание, что эти функции охватывают всю логику и только логику системы. Это позволяет вам тестировать логику без побочных эффектов и продвигает хорошую архитектуру. Также обратите внимание, что они возвращают Driver
с. Вы можете быть уверены, что ни одна из этих функций не выдаст ошибку, которая разорвет цепочку и поведение контроллера представления.
/// Emits hubs that need to be persisted.
func hubPersist(location: ControlEvent<Location>, userInfo: UserInfo) -> Driver<(location: Location, hub: Hub)> {
let hub = getHub(location: location, userInfo: userInfo)
.asDriver(onErrorRecover: { _ in fatalError("no errors are possible") })
return Driver.combineLatest(location.asDriver(), hub) { (location: $0, hub: $1) }
}
/// Values emitted by this function are used to make the network request.
func networkInfo(location: ControlEvent<Location>, userInfo: UserInfo) -> Driver<(NetworkService, Int)> {
let hub = getHub(location: location, userInfo: userInfo)
return Observable.combineLatest(hub, location.asObservable())
.compactMap { (hub, location) -> (NetworkService, Int)? in
guard let hubToken = hub.hubToken else { return nil }
return (NetworkService(plugins: [AuthPlugin(token: hubToken)]), location.id)
}
.asDriver(onErrorRecover: { _ in fatalError("no errors are possible") })
}
/// shared logic used by both of the above. Testing the above will test this by default.
func getHub(location: ControlEvent<Location>, userInfo: UserInfo) -> Observable<Hub> {
return location
.compactMap { location -> Hub? in
let hubs = userInfo.authorizedHubLocations
return hubs.first(where: { $0.locationId == location.id })
}
}
Функция ниже представляет собой оболочку для вашего сетевого запроса, которая делает ошибки более пригодными для использования.
extension NetworkService {
func getJobStates(locationId: Int) -> Driver<Result<[JobState], Error>> {
return jobStates(locationId: locationId)
.map { .success($0 ?? []) }
.asDriver(onErrorRecover: { Driver.just(.failure($0)) })
}
}
Вот ваш код контроллера представления, использующий все вышеперечисленное. Он состоит почти исключительно из побочных эффектов. Единственная логика - пара охранников для проверки на успешность / сбой сетевого запроса.
func viewDidLoad() {
super.viewDidLoad()
hubPersist(location: locationsTable.rx.modelSelected(Location.self), userInfo: viewModel.userInfo)
.drive(onNext: { [databaseService] location, hub in
databaseService?.persistHub(hubResult: hub, location: location)
})
.disposed(by: disposeBag)
let jobStates = networkInfo(location: locationsTable.rx.modelSelected(Location.self), userInfo: viewModel.userInfo)
.flatMapLatest { networkService, locationId in
return networkService.getJobStates(locationId: locationId)
}
jobStates
.drive(onNext: { [databaseService] jobStates in
guard case .success(let state) = jobStates else { return }
databaseService?.persistJobStates(jobStates: state)
})
.disposed(by: disposeBag)
jobStates
.drive(onNext: { jobStates in
guard case .success = jobStates else { return }
NavigationService.renderScreenB()
})
.disposed(by: disposeBag)
jobStates
.drive(onNext: { jobStates in
guard case .failure = jobStates else { return }
Banner.showBanner(type: .error, title: "Whoops", message: "Something went wrong.")
})
.disposed(by: disposeBag)
}
FYI, приведенный выше код использует Swift 5 / RxSwift 5.