【问题标题】:Confusion about flatMapLatest in RxSwift对 RxSwift 中的 flatMapLatest 感到困惑
【发布时间】:2016-09-24 03:58:23
【问题描述】:

我在 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 的文章,但没有一个能解释我的困惑。

我看到的是这样的:

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

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

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

【问题讨论】:

    标签: swift functional-programming reactive-cocoa rx-swift


    【解决方案1】:

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

    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 { $0 as NSError }, interval))
                        #else
                            print(convertResponseToString(response, error.map { $0 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)
            }
        }
    

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

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

        将 Observable 序列发射的元素转换为 Observable 序列,并将两个 Observable 序列的发射合并为一个 Observable 序列。 这也很有用,例如,当您有一个本身发出 Observable 序列的 Observable 序列,并且您希望能够对来自任一 Observable 序列的新发射作出反应时。 flatMap 和 flatMapLatest 的区别在于,flatMapLatest 只会从最近的内部 Observable 序列中发出元素。

            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 { $0.score.asObservable() } // Change flatMap to flatMapLatest and observe change in printed output
                .subscribe(onNext: { print($0) })
                .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
        

        在此示例中,使用 flatMap 可能会产生意想不到的后果。后 将 ?? 分配给 player.value,??.score 将开始发出 元素,但之前的内部 Observable 序列(??.score)将 也仍然发射元素。通过将 flatMap 更改为 flatMapLatest,仅 最近的内部 Observable 序列(??.score)将发出 元素,即将??.score.value 设置为 95 无效。

        flatMapLatest其实是map和switchLatest的组合 运营商。

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

        flatMap

        跟上它创建的每一个可观察对象,每个添加到源可观察对象的元素一个

        flatMapLatest

        flatMapLatest 的不同之处在于它会自动切换到最新的 observable 并取消订阅前一个。

        【讨论】:

          【解决方案4】:

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

          FlatMapLatest diagram

          好吧,flatMap() 得到一个值,然后执行长任务,当它 获取下一个值,即使新的任务仍然会完成 值到达当前任务的中间。这不是真的 我们需要,因为当我们在搜索栏中获得新文本时,我们想要 取消上一个请求并开始另一个。就是这样 flatMapLatest() 可以。

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

          您可以使用 Appstore 上的 RxMarbles 应用与操作员一起玩。

          【讨论】:

            【解决方案5】:

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

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

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

            【讨论】:

            • input.name 没有改变!只有一个Observable。 flatMapLatest 中的闭包每次调用,但网络请求只返回一次。
            • 我得到了关于flatMapLatest的另一种解释,它解决了我的困惑。 github.com/baconjs/bacon.js/wiki/Diagrams 。谢谢。
            猜你喜欢
            • 1970-01-01
            • 2019-09-13
            • 2012-07-22
            • 2013-05-13
            • 2020-04-16
            • 2023-03-08
            • 2019-08-04
            • 2019-12-30
            • 2022-01-20
            相关资源
            最近更新 更多