关于 RxSwift 中 flatMapLatest 的困惑

Confusion about flatMapLatest in RxSwift

我学习了RxSwift中的示例代码。在文件GithubSignupViewModel1.swift中,validatedUsername的定义是:

validatedUsername = input.username //the username is a textfiled.rx_text
    .flatMapLatest { username -> Observable<ValidationResult> in
        print("-------->1:")
        return validationService.validateUsername(username)
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(.Failed(message: "Error contacting server"))
    }
    .shareReplay(1)

validateUsername 方法最终调用如下方法:

func usernameAvailable(username: String) -> Observable<Bool> {
    // this is ofc just mock, but good enough
    print("-------->2:")
    let URL = NSURL(string: "https://github.com/\(username.URLEscaped)")!
    let request = NSURLRequest(URL: URL)
    return self.URLSession.rx_response(request)
        .map { (maybeData, response) in
            print("-------->3:")
            return response.statusCode == 404
        }
        .catchErrorJustReturn(false)
}

这是我的困惑:

每当我在用户名文本字段中快速输入一个字符时,消息 -------->1:, -------->2: 显示,稍晚一点的消息 --- ----->3: 显示,但只显示了一条 ------>3: 消息。

当我输入字符较慢时,消息-------->1:,-------->2:,-------->3:依次显示。

但是当我将flatMapLatest 更改为flatMap 时,输入多少个字符,我将得到相同数量的-------->3: 消息。

那么 flatMapLatest 在这里是如何工作的?

flatMapLatest 如何过滤来自 NSURLResponse 的早期响应?


我阅读了一些关于 flatMapLatest 的内容,但是 none 可以解释我的困惑。

我看到的是这样的:

let a = Variable(XX)
a.asObservable().flatMapLatest(...)

a.value更改为另一个变量时,变量(XX) 不会影响a 的订阅者。

但是input.username没有改变,它总是testfield.rx_text!那么 flatMapLatest 是如何工作的呢?

不清楚你的困惑是什么。您是在质疑 flatMapflatMapLatest 之间的区别吗? flatMap 将映射到一个新的 Observable,如果它需要再次 flatMap,它实际上会将两个映射的 Observable 合并为一个。如果需要再次flatMap,它会再次合并它,等等

使用 flatMapLatest,当映射新的 Observable 时,它会覆盖最后一个 Observable(如果有的话)。没有合并。

编辑: 作为对您评论的回应,您没有看到任何 "------>3:" 印刷品的原因是那些 rx_request Observable 在它们可以竞争之前就被处理掉了,因为 flatMapLatest 收到了一个新的元素,并将其映射到新的 Observable。处置后,rx_request 可能会取消请求并且不会 运行 您正在打印的回调。旧的 Observable 已被处置,因为当新的取代它时它不再属于任何人。

TheDroidsOnDroid 的回答对我来说很清楚:

FlatMapLatest diagram

Well, flatMap() gets one value, then performs long task, and when it gets the next value, previous task will still finish even when the new value arrives in the middle of the current task. It isn’t really what we need because when we get a new text in the search bar, we want to cancel the previous request and start another. That’s what flatMapLatest() does.

http://www.thedroidsonroids.com/blog/ios/rxswift-examples-3-networking/

您可以使用 Appstore 上的 RxMarbles 应用来玩操作员。

我觉得这个https://github.com/ReactiveX/RxSwift/blob/master/Rx.playground/Pages/Transforming_Operators.xcplaygroundpage/Contents.swift很有用

Transforms the elements emitted by an Observable sequence into Observable sequences, and merges the emissions from both Observable sequences into a single Observable sequence. This is also useful when, for example, when you have an Observable sequence that itself emits Observable sequences, and you want to be able to react to new emissions from either Observable sequence. The difference between flatMap and flatMapLatest is, flatMapLatest will only emit elements from the most recent inner Observable sequence.

    let disposeBag = DisposeBag()

    struct Player {
        var score: Variable<Int>
    }

    let  = Player(score: Variable(80))
    let  = Player(score: Variable(90))

    let player = Variable()

    player.asObservable()
        .flatMap { [=10=].score.asObservable() } // Change flatMap to flatMapLatest and observe change in printed output
        .subscribe(onNext: { print([=10=]) })
        .disposed(by: disposeBag)

    .score.value = 85

    player.value = 

    .score.value = 95 // Will be printed when using flatMap, but will not be printed when using flatMapLatest

    .score.value = 100

有了flatMap,我们得到

80
85
90
95
100

flatMapLatest,我们得到

80
85
90
100

In this example, using flatMap may have unintended consequences. After assigning to player.value, .score will begin to emit elements, but the previous inner Observable sequence (.score) will also still emit elements. By changing flatMap to flatMapLatest, only the most recent inner Observable sequence (.score) will emit elements, i.e., setting .score.value to 95 has no effect.

flatMapLatest is actually a combination of the map and switchLatest operators.

此外,我发现 https://www.raywenderlich.com/158205/rxswift-transforming-operators 这很有用

flatMap

keeps up with each and every observable it creates, one for each element added onto the source observable

flatMapLatest

What makes flatMapLatest different is that it will automatically switch to the latest observable and unsubscribe from the the previous one.

我认为 Ray Wenderlich tutorial 中的这张图表可以提供帮助。

因此,如果我们考虑在用户每次输入事件时从搜索框中发出一个事件,flatMap 将触发一个单独的请求,即使当前请求正在运行中也是如此。 flatMapLatest 相比之下处理第一个流。在 URLSession 的包装器中,这会调用取消请求。因此,如果您输入得非常快,您应该会看到更少的请求返回。这里有一段精彩的视频对此进行了解释:https://youtu.be/z8ukiv5flcw。这是 URLSession 包装器的源代码(注意 task.cancel 处理):

public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
        return Observable.create { observer in

            // smart compiler should be able to optimize this out
            let d: Date?

            if URLSession.rx.shouldLogRequest(request) {
                d = Date()
            }
            else {
               d = nil
            }

            let task = self.base.dataTask(with: request) { data, response, error in

                if URLSession.rx.shouldLogRequest(request) {
                    let interval = Date().timeIntervalSince(d ?? Date())
                    print(convertURLRequestToCurlCommand(request))
                    #if os(Linux)
                        print(convertResponseToString(response, error.flatMap { [=10=] as NSError }, interval))
                    #else
                        print(convertResponseToString(response, error.map { [=10=] as NSError }, interval))
                    #endif
                }
                
                guard let response = response, let data = data else {
                    observer.on(.error(error ?? RxCocoaURLError.unknown))
                    return
                }

                guard let httpResponse = response as? HTTPURLResponse else {
                    observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
                    return
                }

                observer.on(.next((httpResponse, data)))
                observer.on(.completed)
            }

            task.resume()

            return Disposables.create(with: task.cancel)
        }
    }