【问题标题】:Proper usage of RxSwift to chain requests, flatMap or something else?正确使用 RxSwift 链接请求、flatMap 或其他东西?
【发布时间】:2017-04-16 15:20:00
【问题描述】:

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

我有两个功能:

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

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

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

所以我想我应该以某种方式订阅 Observable 但我不知道该怎么做。

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

//编辑

感谢 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&lt;[TaskDetails]&gt;) 的结果不会产生“onNext”。

// 再次编辑

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

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

【问题讨论】:

    标签: ios swift rx-swift


    【解决方案1】:

    对于 RxSwift,您希望尽可能使用 Observables,因此我建议您重构 downloadAllTasks 方法以返回 Observable&lt;Task&gt;。这应该是相当简单的,只需循环遍历元素而不是直接发出数组:

    // 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() -&gt; Observable&lt;Task&gt; 方法,因为它是更简洁的方法。

    然后你可以map你的任务来获取他们的id(假设你的Task类型有id: Int64属性)和flatMap使用downloadAllTasks函数来获取Observable&lt;TaskDetails&gt;

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

    【讨论】:

    • 很好和干净的解释,非常感谢。它几乎可以按我的意愿工作。几乎,因为我对最后一部分有问题 - toArray()。如果没有这部分,订阅者会收到有关任务详细信息下载事件的正确通知。使用 toArray,我希望在下载所有任务详细信息时,我会在订阅者的 onNext 中收到通知,但根本不会调用 onNext。您知道可能是什么问题吗?
    • @Wujo 当单个任务详情下载遇到错误时,最终的Observable会报错。您应该可以在.toArray() 之前使用.debug() 进行检查。我编辑了我的答案,如果你希望它完成,即使有错误
    • 问题不是getTaskDetails时出错,所有任务详情都正常下载。我编辑了我的原始帖子,试图更清楚地指出它。
    • @Wujo 你确定你没有忘记observable.onComplete() 生成代码中的observable.onComplete() 吗?你能分享一下那些的(简化的)实现吗?因为我测试了它并且它对我有用
    • 不幸的是我不能共享代码,因为它有一些额外的依赖等更复杂。但你是对的,我没有在任何地方调用 onComplete,但我应该在哪里调用它?来自 downloadAllTask​​s?
    【解决方案2】:

    有一个比公认的答案更简单的解决方案:

    downloadAllTasks()
        .flatMap { tasks in
            Observable.zip(tasks.map { getTaskDetails(taskId: $0.id) })
        }
    

    没有必要将数组分解成一堆单独的 Observable,然后再尝试将它们全部塞回数组中。 zip 运算符将获取一个 Observable 数组并将它们转换为一个包含数组的单个 Observable。

    请注意,在上述情况下,如果任何一个 getTaskDetails 调用失败,则整个流将失败,而在接受的答案中,如果 getTaskDetails 失败,则相关任务将默默地从数组中删除。我不确定这些解决方案中的任何一个是否良好。

    更好的是,我认为是传递 Task 对象,以便调用代码知道它存在,即使它没有所有详细信息。像这样的:

    struct TaskGroup {
        let task: Task
        let details: TaskDetails?
    }
    
    func example() -> Observable<[TaskGroup]> {
        downloadAllTasks()
            .flatMap { tasks in
                Observable.zip(tasks.map { task in
                    getTaskDetails(taskId: task.id)
                        .map { TaskGroup(task: task, details: $0) }
                        .catch { _ in Observable.just(TaskGroup(task: task, details: nil)) }
                })
            }
    }
    

    这里的区别在于flatMap 中的内容。如果getTaskDetails 成功,将创建一个包含任务及其详细信息的任务组。如果发出错误,则不会像接受的答案那样忽略错误,而是会发出一个包含任务但细节为零的 TaskGroup。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-12
      • 1970-01-01
      • 1970-01-01
      • 2012-08-22
      • 2018-09-19
      • 1970-01-01
      • 1970-01-01
      • 2012-11-29
      相关资源
      最近更新 更多