Proper usage of RxSwift to chain requests, flatMap or something else?

笑着哭i 提交于 2019-12-03 07:30:33

With RxSwift you want to use Observables 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()
        }
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!