正确使用RxSwift链接请求,flatMap或其他什么? [英] Proper usage of RxSwift to chain requests, flatMap or something else?
问题描述
首先,我是rxswift的新手,所以我猜答案是显而易见的,但目前我自己找不到解决方案。
First of all, I'm new to rxswift so I guess the answer is obvious however at the moment I can't find solution by myself.
我有两个函数:
func downloadAllTasks() -> Observable<[Task]>
func getTaskDetails(taskId: Int64) -> Observable<TaskDetails>
第一个是使用网络请求下载Task对象列表,第二个是为特定任务下载任务详细信息(使用它的id)
First one is downloading the list of Task objects using network request, second one downloading task details for sepcific task (using it's id)
我想要实现的是下载所有任务,然后为每个任务我要下载其详细信息并订阅所有任务时触发的事件细节已准备好。
What I want of achieve is to download all tasks and then for each task I want to download its details and subscribe for the event fired when all tasks details are ready.
所以我想我应该以某种方式订阅Observable< [TaskDetails]>但我不知道该怎么做。
So I guess I should subscribe somehow to Observable<[TaskDetails]> but I don't know how to do it.
downloadAllTasks()
.flatMap{
... // flatMap? something else?
}
.subscribe(
onNext: { details in
print("tasks details: \(details.map{$0.name})")
})
.addDisposableTo(disposeBag)
//编辑
感谢Silvan Mosberger回答我更接近解决方案。还有一个问题。现在我有这样的事情:
Thanks to Silvan Mosberger answer I'm much closer to the solution. One problem left. Now I have something like this:
downloadAllTasks()
.flatMap{ Observable.from($0) }
.map{ $0.id }
.flatMap{ [unowned self] id in
self.getTaskDetails(taskId: id).catchError{ error in
print("$$$ Error downloading task \(id)")
return .empty()
}
}
.do(onNext: { _ in
print(" $$$ single task details downloaded")
} )
.toArray()
.debug("$$$ task details array debug", trimOutput: false)
.subscribe({ _ in
print("$$$ all tasks downloaded")
})
.addDisposableTo(disposeBag)
输出
$$$ task details array debug -> subscribed
$$$ single task details downloaded
$$$ single task details downloaded
$$$ single task details downloaded
有3个任务可用,因为你可以正确地下载所有这些任务但是由于某种原因toArray()的结果 - ( Observable< [一旦所有任务细节都准备就绪,TaskDetails]>
)不会产生onNext。
There are 3 tasks available so as you can se all of them are downloaded properly however for some reason the result of toArray() - (Observable<[TaskDetails]>
) doesn't produce "onNext" once all task details are ready.
//再次编辑
好的,我正在添加提供可观察量的函数的简化版本,也许它会有所帮助
Ok, I'm adding simplified version of functions providing observables, maybe it will help something
func downloadAllTasks() -> Observable<Task> {
return Observable.create { observer in
//... network request to download tasks
//...
for task in tasks {
observer.onNext(task)
}
observer.onCompleted()
return Disposables.create()
}
}
func getTaskDetails(id: Int64) -> Observable< TaskDetails > {
return Observable.create { observer in
//... network request to download task details
//...
observer.onNext(taskDetails)
return Disposables.create()
}
}
推荐答案
使用RxSwift,您希望尽可能使用 Observable
,因此我建议您重构 downloadAllTasks
返回 Observable< Task>
的方法。通过循环遍历元素而不是直接发出数组,这应该是相当简单的:
With RxSwift you want to use Observable
s whenever possible, therefore I recommend you to refactor the downloadAllTasks
method to return an Observable<Task>
. This should be fairly trivial by just looping through the elements instead of emitting the array directly:
// In downloadAllTasks() -> Observable<Task>
for task in receivedTasks {
observable.onNext(task)
}
如果由于某种原因无法做到这一点,那么在RxSwift中还有一个运算符:
If this is not possible for whatever reason, there is also an operator for that in RxSwift:
// Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task>
downloadAllTasks().flatMap{ Observable.from($0) }
在以下代码中我将使用重构的 downloadAllTasks() - > Observable< Task>
方法因为它是更干净的方法。
In the following code I will be using the refactored downloadAllTasks() -> Observable<Task>
method because it's the cleaner approach.
然后你可以 map
你的任务得到他们的id(假设你的任务
类型有 id:Int64
属性)和 flatMap
使用 downloadAllTasks
函数获取 Observable< TaskDetails>
:
You can then map
your tasks to get their id (assuming your Task
type has the id: Int64
property) and flatMap
with the downloadAllTasks
function to get an Observable<TaskDetails>
:
let details : Observable<TaskDetails> = downloadAllTasks()
.map{ $0.id }
.flatMap(getTaskDetails)
然后你可以使用 toArray()
运算符来收集整个序列并发出一个包含数组中所有元素的事件:
Then you can use the toArray()
operator to gather the whole sequence and emit an event containing all elements in an array:
let allDetails : Observable<[TaskDetails]> = details.toArray()
简而言之,没有类型注释和共享任务(所以你赢了' t只下载一次):
In short, without type annotations and sharing the tasks (so you won't download them only once):
let tasks = downloadAllTasks().share()
let allDetails = tasks
.map{ $0.id }
.flatMap(getTaskDetails)
.toArray()
编辑:请注意,当任何详细信息下载遇到错误时,此Observable将出错。我不确定防止这种情况的最佳方法是什么,但这确实有效:
Note that this Observable will error when any of the detail downloads encounters an error. I'm not exactly sure what's the best way to prevent this, but this does work:
let allDetails = tasks
.map{ $0.id }
.flatMap{ id in
getTaskDetails(id: id).catchError{ error in
print("Error downloading task \(id)")
return .empty()
}
}
.toArray()
EDIT2:如果你的 getTaskDetails
返回一个永不完成的observable,它就不会工作。这是 getTaskDetails
的简单参考实现(使用 String
而不是 TaskDetails
),使用 JSONPlaceholder :
It's not gonna work if your getTaskDetails
returns an observable that never completes. Here is a simple reference implementation of getTaskDetails
(with String
instead of TaskDetails
), using JSONPlaceholder:
func getTaskDetails(id: Int64) -> Observable<String> {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(id)")!
return Observable.create{ observer in
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
observer.onError(error)
} else if let data = data, let result = String(data: data, encoding: .utf8) {
observer.onNext(result)
observer.onCompleted()
} else {
observer.onError("Couldn't get data")
}
}
task.resume()
return Disposables.create{
task.cancel()
}
}
}
这篇关于正确使用RxSwift链接请求,flatMap或其他什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!