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

Wuj*_*ujo 7 ios swift rx-swift

首先,我是rxswift的新手所以我想答案是显而易见的,但此刻我自己找不到解决方案.

我有两个功能:

func downloadAllTasks() -> Observable<[Task]>
func getTaskDetails(taskId: Int64) -> Observable<TaskDetails>
Run Code Online (Sandbox Code Playgroud)

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

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

所以我想我应该以某种方式订阅Observable <[TaskDetails]>,但我不知道该怎么做.

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

//编辑

感谢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)
Run Code Online (Sandbox Code Playgroud)

输出是

$$$ task details array debug -> subscribed
$$$ single task details downloaded
$$$ single task details downloaded
$$$ single task details downloaded
Run Code Online (Sandbox Code Playgroud)

有3个任务可用,因此你可以正确地下载所有任务但是由于某种原因,Observable<[TaskDetails]>一旦所有任务细节都准备就绪,toArray() - ()的结果就不会产生"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()
    }
}
Run Code Online (Sandbox Code Playgroud)

Sil*_*ger 12

使用RxSwift,您希望尽可能使用Observables,因此我建议您重构该downloadAllTasks方法以返回Observable<Task>.通过循环遍历元素而不是直接发射数组,这应该是相当简单的:

// In downloadAllTasks() -> Observable<Task>
for task in receivedTasks {
    observable.onNext(task)
}
Run Code Online (Sandbox Code Playgroud)

如果由于某种原因无法做到这一点,那么在RxSwift中还有一个运算符:

// Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task>
downloadAllTasks().flatMap{ Observable.from($0) }
Run Code Online (Sandbox Code Playgroud)

在下面的代码中,我将使用重构downloadAllTasks() -> Observable<Task>方法,因为它是更干净的方法.

然后,您可以将map您的任务获取其ID(假设您的Task类型具有id: Int64属性)并flatMap使用downloadAllTasks函数获取Observable<TaskDetails>:

let details : Observable<TaskDetails> = downloadAllTasks()
    .map{ $0.id }
    .flatMap(getTaskDetails)
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用toArray()运算符来收集整个序列并发出包含数组中所有元素的事件:

let allDetails : Observable<[TaskDetails]> = details.toArray()
Run Code Online (Sandbox Code Playgroud)

简而言之,没有类型注释和共享任务(所以你不会只下载一次):

let tasks = downloadAllTasks().share()

let allDetails = tasks
    .map{ $0.id }
    .flatMap(getTaskDetails)
    .toArray()
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,当任何详细信息下载遇到错误时,此Observable将出错.我不确定防止这种情况的最佳方法是什么,但这确实有效:

let allDetails = tasks
    .map{ $0.id }
    .flatMap{ id in
        getTaskDetails(id: id).catchError{ error in
            print("Error downloading task \(id)")
            return .empty()
        }
    }
    .toArray()
Run Code Online (Sandbox Code Playgroud)

编辑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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)