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)
}
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) }
In the following code I will be using the refactored downloadAllTasks() -> Observable<Task>
method because it's the cleaner approach.
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)
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()
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()
EDIT: 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: 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()
}
}
}