正确使用RxSwift链接请求,flatMap或其他什么东西?

时间:2016-12-01 20:37:10

标签: ios swift rx-swift

首先,我是rxswift的新手,所以我想答案很明显,但目前我找不到自己的解决方案。

我有两个功能:

func downloadAllTasks() -> Observable<[Task]>
func getTaskDetails(taskId: Int64) -> Observable<TaskDetails>

第一个是使用网络请求下载Task对象列表,第二个是下载sepcific任务的任务详细信息(使用它的id)

我想要实现的是下载所有任务,然后为每个任务我要下载其详细信息并订阅所有任务详细信息准备好后触发的事件。

所以我想我应该以某种方式订阅Observable&lt; [TaskDetails]&gt;但我不知道怎么做。

        downloadAllTasks()
        .flatMap{
            ... // flatMap? something else?
        }
        .subscribe(
            onNext: { details in
                print("tasks details: \(details.map{$0.name})")
        })
        .addDisposableTo(disposeBag)

// EDIT

感谢Silvan Mosberger的回答,我更接近解决方案。还有一个问题。现在我有这样的事情:

    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”

//再次编辑

好的,我正在添加提供observables的简化版本的函数,也许它会有所帮助

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()
    }
}

1 个答案:

答案 0 :(得分:12)

使用RxSwift,您希望尽可能使用Observable,因此我建议您重构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类型具有id: Int64属性)和flatMap具有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()

EDIT2:如果你的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()
        }
    }
}