正确使用RxSwift来链接请求,flatMap或其他东西?
冷杉的整个我是rxswift新来的,所以我想答案是显而易见的,但目前我无法自己find解决scheme。
我有两个function:
func downloadAllTasks() -> Observable<[Task]> func getTaskDetails(taskId: Int64) -> Observable<TaskDetails>
第一个是使用networking请求下载任务对象列表,第二个下载任务详细信息的特定任务(使用它的ID)
我想要实现的是下载所有任务,然后为每个任务我想下载它的细节,并订阅所有任务细节准备就绪的事件。
所以我想我应该订阅Observable <[TaskDetails]>,但我不知道该怎么做。
downloadAllTasks() .flatMap{ ... // flatMap? something else? } .subscribe( onNext: { details in print("tasks details: \(details.map{$0.name})") }) .addDisposableTo(disposeBag)
//编辑
感谢Silvan Mosberger的回答,我更接近解决scheme。 还有一个问题。 现在我有这样的东西:
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”。
//再次编辑
好吧,我添加了提供observablesfunction的简化版本,也许它会帮助一些东西
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
,因此我build议您重构downloadAllTasks
方法以返回Observable<Task>
。 这只需循环遍历元素而不是直接发射数组,这应该是相当简单的:
// In downloadAllTasks() -> Observable<Task> for task in receivedTasks { observable.onNext(task) }
如果无论出于何种原因这样做是不可能的,那么在RxSwift中也有一个操作符:
// Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task> downloadAllTasks().flatMap{ Observable.from($0) }
在下面的代码中,我将使用重构的downloadAllTasks() -> Observable<Task>
方法,因为它是更清晰的方法。
然后,您可以map
您的任务以获取其id(假设您的Task
types具有id: Int64
属性),并使用downloadAllTasks
函数获取一个Observable<TaskDetails>
:
let details : Observable<TaskDetails> = downloadAllTasks() .map{ $0.id } .flatMap(getTaskDetails)
然后,可以使用toArray()
运算符来收集整个序列,并发出包含数组中所有元素的事件:
let allDetails : Observable<[TaskDetails]> = details.toArray()
总之,没有键入注释和共享任务(所以你不会只下载一次):
let tasks = downloadAllTasks().share() let allDetails = tasks .map{ $0.id } .flatMap(getTaskDetails) .toArray()
编辑:请注意,当任何细节下载遇到错误时,此Observable将会出错。 我不完全确定什么是防止这种情况的最佳方法,但是这确实起作用:
let allDetails = tasks .map{ $0.id } .flatMap{ id in getTaskDetails(id: id).catchError{ error in print("Error downloading task \(id)") return .empty() } } .toArray()
编辑2:它不会工作,如果你的getTaskDetails
返回一个永远不会完成的观察。 下面是getTaskDetails
(使用String
而不是TaskDetails
)的简单参考实现,使用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() } } }