RxSwift 如何根据之前的结果跳过地图?

RxSwift how to skip map depending on previous result?

我正在尝试下载一些 json,解析它,检查 json 中的一些信息,并根据结果继续处理。

最符合 RxSwift 习惯的做法是什么?

        URLSession.shared.rx
            .data(request:request)
            .observe(on: ConcurrentDispatchQueueScheduler(qos: .background))
            .flatMap(parseJson) // into ModelObject
            .flatMap(checkModel) // on some condition is there any way to jump into the onCompleted block? if the condition is false then execute processObject
            .map(processObject)
            .subscribe(
                onError: { error in
                print("error: \(error)")
            }, onCompleted: {
                print("Completed with no error")
            })
            .disposed(by: disposeBag)

其中 parseJson 类似于:

func parseJson(_ data: Data) -> Single<ModelObject>

checkModel 做一些检查,如果满足某些条件应该完成序列而不以 processObject

结尾
func checkModel(_ modelObject: ModelObject) -> Single<ModelObject> {
  //probably single is not what I want here
}

最后 processObject

func processObject(_ modelObject: ModelObject) -> Completable {
}

这是一个很难回答的问题,因为一方面你问了一个关于跳过 map 的简单问题,而另一方面你问的是“大多数 RxSwift 惯用的方式来做这个," 这需要比简单地跳转 map.

更多的改变

如果我只是回答基本问题。解决方案是 checkModel return Maybe 而不是 Single。


从“使其更加地道”的角度来看这段代码,还需要进行一些更改。我要说的很多内容都来自基于函数名称的假设和对您要实现的目标的期望。随着我的进行,我将尝试提出这些假设...

.observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) 可能没有必要。 URLSession 已经在后台发出。

parseJson 函数可能 而不是 return 一个 Observable 类型。它应该只是 return 一个 ModelObject。这假设函数是纯的;它不执行任何副作用,只是将数据转换为 ModelObject。

func parseJson(_ data: Data) throws -> ModelObject

checkModel 函数可能 不是 return 一个 Observable 类型。这听起来确实应该 return 一个 Bool 并用于过滤不需要进一步处理的模型对象。这里我再次假设该函数是纯函数,它不执行任何副作用,它只是检查模型。

func checkModel(_ modelObject: ModelObject) -> Bool

最后,processObject 函数可能有副作用。它可能是数据的消费者,因此根本不应该 return 任何东西(即,它应该 return 无效。)

func processObject(_ modelObject: ModelObject)

更新: 在你的评论中你说你想以一个 Completable 结束。即便如此,我也不希望此函数 return 成为可完成的,因为那样会使它变得懒惰,因此即使您只想调用它的效果也需要您订阅。

您可以创建一个通用的 wrap 运算符来将任何有副作用的函数变成 Completable:

extension Completable {
    static func wrap<T>(_ fn: @escaping (T) -> Void) -> (T) -> Completable {
        { element in
            fn(element)
            return Completable.empty()
        }
    }
}

如果以上函数按照上面的讨论进行调整,那么Observable链就变成了:

let getAndProcess = URLSession.shared.rx.data(request:request)
    .map(parseJson)
    .filter(checkModel)
    .flatMap(Completable.wrap(processObject))
    .asCompletable()

以上将生成一个 Completable,每次订阅时都会执行流程。

通过这种方式设置,您会发现您的基本功能更容易测试。您不需要任何特殊的基础设施,甚至不需要 RxText 来确保它们是正确的。此外,很明显 parseJsoncheckModel 没有执行任何副作用。

这个想法是要有一个“功能核心,势在必行 Shell”。命令式位(在这种情况下是数据请求和处理)被移到边缘,而订阅的核心保持纯粹的功能并且易于 test/understand.