重新组织链接的可观察对象
Re-organizing chained observables
我有一大块链式 Rx observables,当通过 table.rx.modelSelected
选择一个 tableviews 行时,它们会被触发。
我希望能够分解此逻辑,因为我目前必须在 flatMapLatest
中执行业务逻辑,因为它是 "Step 1" 流程(感觉不对) ,我还要在后续的subscribe
("Step 2")中执行更多的业务逻辑。这是我正在使用的代码:
locationsTable.rx.modelSelected(Location.self)
.flatMapLatest { [weak self] location -> Observable<[JobState]?> in
guard let hubs = self?.viewModel.userInfo.authorizedHubLocations else { return .empty() }
guard let hub = hubs.first(where: { [=10=].locationId == location.id }) else { return .empty() }
guard let hubToken = hub.hubToken else { return .empty() }
// save data in db
self?.databaseService.persistHub(hubResult: hub, location: location)
// make network call for the 2nd step (the subscribe)
let networkService = NetworkService(plugins: [AuthPlugin(token: hubToken)])
return networkService.jobStates(locationId: location.id)
}
.subscribe(onNext: { [weak self] jobState in
if let jobState = jobState {
self?.databaseService.persistJobStates(jobStates: jobState)
}
NavigationService.renderScreenB()
}, onError: { error in
Banner.showBanner(type: .error, title: "Whoops", message: "Something went wrong.")
}).disposed(by: disposeBag)
此代码目前有效,但感觉很脏。任何关于如何清理它的建议将不胜感激。
您有几个独立且不同的逻辑位,并且 side-effects 并且您正试图将它们全部塞入一个 flatMap 中。我建议将它们分解成它们的组成部分。
另外,您的错误逻辑不正确。如果您的网络服务发出错误,您的 "Whoops" 横幅将显示,但它也会破坏您的链,用户将无法 select 其他位置。我下面的代码解决了这个问题。
以下功能均为免费功能。由于它们不依赖于特定的视图控制器,因此可以独立使用和测试。另请注意,这些函数包含 all 逻辑和 only 系统逻辑。这使您可以免费测试逻辑 side-effects 并促进良好的体系结构。还要注意他们 return Driver
s。您可以确定这些函数中的 none 会发出错误,这会破坏链和视图控制器的行为。
/// 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: [=10=], hub: ) }
}
/// 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: { [=10=].locationId == location.id })
}
}
下面的函数是您的网络请求的包装器,它使错误更有用。
extension NetworkService {
func getJobStates(locationId: Int) -> Driver<Result<[JobState], Error>> {
return jobStates(locationId: locationId)
.map { .success([=11=] ?? []) }
.asDriver(onErrorRecover: { Driver.just(.failure([=11=])) })
}
}
这是使用以上所有内容的视图控制器代码。它几乎完全由副作用组成。唯一的逻辑是几个守卫检查网络请求的 success/failure。
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)
}
仅供参考,上面的代码使用 Swift 5/RxSwift 5.
我有一大块链式 Rx observables,当通过 table.rx.modelSelected
选择一个 tableviews 行时,它们会被触发。
我希望能够分解此逻辑,因为我目前必须在 flatMapLatest
中执行业务逻辑,因为它是 "Step 1" 流程(感觉不对) ,我还要在后续的subscribe
("Step 2")中执行更多的业务逻辑。这是我正在使用的代码:
locationsTable.rx.modelSelected(Location.self)
.flatMapLatest { [weak self] location -> Observable<[JobState]?> in
guard let hubs = self?.viewModel.userInfo.authorizedHubLocations else { return .empty() }
guard let hub = hubs.first(where: { [=10=].locationId == location.id }) else { return .empty() }
guard let hubToken = hub.hubToken else { return .empty() }
// save data in db
self?.databaseService.persistHub(hubResult: hub, location: location)
// make network call for the 2nd step (the subscribe)
let networkService = NetworkService(plugins: [AuthPlugin(token: hubToken)])
return networkService.jobStates(locationId: location.id)
}
.subscribe(onNext: { [weak self] jobState in
if let jobState = jobState {
self?.databaseService.persistJobStates(jobStates: jobState)
}
NavigationService.renderScreenB()
}, onError: { error in
Banner.showBanner(type: .error, title: "Whoops", message: "Something went wrong.")
}).disposed(by: disposeBag)
此代码目前有效,但感觉很脏。任何关于如何清理它的建议将不胜感激。
您有几个独立且不同的逻辑位,并且 side-effects 并且您正试图将它们全部塞入一个 flatMap 中。我建议将它们分解成它们的组成部分。
另外,您的错误逻辑不正确。如果您的网络服务发出错误,您的 "Whoops" 横幅将显示,但它也会破坏您的链,用户将无法 select 其他位置。我下面的代码解决了这个问题。
以下功能均为免费功能。由于它们不依赖于特定的视图控制器,因此可以独立使用和测试。另请注意,这些函数包含 all 逻辑和 only 系统逻辑。这使您可以免费测试逻辑 side-effects 并促进良好的体系结构。还要注意他们 return Driver
s。您可以确定这些函数中的 none 会发出错误,这会破坏链和视图控制器的行为。
/// 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: [=10=], hub: ) }
}
/// 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: { [=10=].locationId == location.id })
}
}
下面的函数是您的网络请求的包装器,它使错误更有用。
extension NetworkService {
func getJobStates(locationId: Int) -> Driver<Result<[JobState], Error>> {
return jobStates(locationId: locationId)
.map { .success([=11=] ?? []) }
.asDriver(onErrorRecover: { Driver.just(.failure([=11=])) })
}
}
这是使用以上所有内容的视图控制器代码。它几乎完全由副作用组成。唯一的逻辑是几个守卫检查网络请求的 success/failure。
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)
}
仅供参考,上面的代码使用 Swift 5/RxSwift 5.