swift - 重新组织链接的可观察对象

标签 swift rx-swift

当通过 table.rx.modelSelected 选择 tableviews 行时,我有相当大的链式 Rx observables block 。

我希望能够分解这个逻辑,因为我目前必须在 flatMapLatest 中执行业务逻辑,因为它是流程的“第 1 步”(感觉不对) ,我必须在后续的 subscribe(“第 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: { $0.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)

此代码目前有效,但感觉很脏。非常感谢任何有关如何清理它的建议。

最佳答案

您有几个独立且不同的逻辑和副作用位,并且您正试图将它们全部塞入一个 flatMap 中。我建议将它们分解成它们的组成部分。

此外,您的错误逻辑也不正确。如果您的网络服务发出错误,您的“哎呀”横幅将显示,但它也会破坏您的链条,用户将无法选择其他位置。我下面的代码解决了这个问题。

以下功能均为免费功能。由于它们不依赖于特定的 View Controller ,因此可以独立使用和测试。另请注意,这些函数包含所有 逻辑和 系统逻辑。这使您可以无副作用地测试逻辑并促进良好的架构。另请注意,它们返回 Driver。您可以确定这些函数都不会发出会破坏链和 View Controller 行为的错误。

/// 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)) })
    }
}

这是使用以上所有内容的 View Controller 代码。它几乎完全由副作用组成。唯一的逻辑是几个守卫来检查网络请求的成功/失败。

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。

关于swift - 重新组织链接的可观察对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56623583/

相关文章:

ios - UITableView 拖放到表外 = 崩溃

ios - 我的 TableViewCell 自定义类中的 IbOutlet 从未被初始化

ios - 解析服务器云代码错误 141 无效函数

ios - 如何解决 Xcode 构建错误 "No such module RxCocoa"?

rx-swift - "Share Side effect"和 "Share Resources"之间的区别

ios - 使用 Swift 5 截取屏幕截图返回黑色图像而不是应用程序的屏幕

ios - 如何使 UIView 匹配 UILabel 的大小

ios - RxSwift - UICollectionView 更新后不更新

ios - RxDataSources 不更新节标题标题

Navigation Controller 的 iOS Swift Coordinator 模式和后退按钮