关于 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 是如何工作的呢?
不清楚你的困惑是什么。您是在质疑 flatMap
和 flatMapLatest
之间的区别吗? flatMap
将映射到一个新的 Observable
,如果它需要再次 flatMap
,它实际上会将两个映射的 Observable
合并为一个。如果需要再次flatMap
,它会再次合并它,等等
使用 flatMapLatest
,当映射新的 Observable
时,它会覆盖最后一个 Observable
(如果有的话)。没有合并。
编辑:
作为对您评论的回应,您没有看到任何 "------>3:"
印刷品的原因是那些 rx_request
Observable
在它们可以竞争之前就被处理掉了,因为 flatMapLatest
收到了一个新的元素,并将其映射到新的 Observable
。处置后,rx_request
可能会取消请求并且不会 运行 您正在打印的回调。旧的 Observable
已被处置,因为当新的取代它时它不再属于任何人。
TheDroidsOnDroid 的回答对我来说很清楚:
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 应用来玩操作员。
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)
}
}
我学习了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 是如何工作的呢?
不清楚你的困惑是什么。您是在质疑 flatMap
和 flatMapLatest
之间的区别吗? flatMap
将映射到一个新的 Observable
,如果它需要再次 flatMap
,它实际上会将两个映射的 Observable
合并为一个。如果需要再次flatMap
,它会再次合并它,等等
使用 flatMapLatest
,当映射新的 Observable
时,它会覆盖最后一个 Observable
(如果有的话)。没有合并。
编辑:
作为对您评论的回应,您没有看到任何 "------>3:"
印刷品的原因是那些 rx_request
Observable
在它们可以竞争之前就被处理掉了,因为 flatMapLatest
收到了一个新的元素,并将其映射到新的 Observable
。处置后,rx_request
可能会取消请求并且不会 运行 您正在打印的回调。旧的 Observable
已被处置,因为当新的取代它时它不再属于任何人。
TheDroidsOnDroid 的回答对我来说很清楚:
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 应用来玩操作员。
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)
}
}