地图中的 RxSwift 多个可观察对象

Posted

技术标签:

【中文标题】地图中的 RxSwift 多个可观察对象【英文标题】:RxSwift multiple observable in map 【发布时间】:2019-04-15 18:58:19 【问题描述】:

我遇到了一种情况,我会获取一个 API,该 API 将生成注册用户的 json 数据。然后我必须遍历每个用户并从远程 url 获取他们的头像并将其保存到磁盘。我可以在subscribe 中执行第二个任务,但这不是最佳实践。我正在尝试使用mapflatMap 等来实现它。

这是我的示例代码:

self.dataManager.getUsers()
            .observeOn(MainScheduler.instance)
            .subscribeOn(globalScheduler)
            .map [unowned self] (data) -> Users in
                var users = data
// other code for manipulating users goes here
// then below I am trying to use another loop to fetch their avatars

                if let cats = users.categories 
                    for cat in cats  
                        if let profiles = cat.profiles 
                            for profile in profiles 
                                if let thumbnail = profile.thumbnail,
                                    let url = URL(string: thumbnail) 
                                    URLSession.shared.rx.response(request: URLRequest(url: url))
                                        .subscribeOn(MainScheduler.instance)
                                        .subscribe(onNext:  response in
                                            // Update Image
                                            if let img = UIImage(data: response.data) 
                                                try? Disk.save(img, to: .caches, as: url.lastPathComponent)
                                            
                                        , onError:  (error) in

                                        ).disposed(by: self.disposeBag)
                                
                            
                        
                    
                

                return users
            
            .subscribe(onSuccess:  [weak self] (users) in

            ).disposed(by: disposeBag)

这段代码有两个问题。首先是URLSession 上的rx,它在另一个线程的后台执行任务,当此操作完成时,无法确认主subscribe。其次是循环和 rx 效率不高,因为它应该生成多个 observables 然后对其进行处理。

欢迎任何改进此逻辑的想法。

【问题讨论】:

我想推荐af_image库来设置头像并摆脱嵌套订阅者。你试过吗? 该库将异步下载图像,我找不到通知主subscribe的方法 它接受一个完成,如果我是你,我会在class 范围内创建一些主要的订阅变量并使用 af_image。否则,您应该处理嵌套订阅。我认为不会有第三种方式。如果有人可以提出另一种方式,我也很感兴趣。所以让我们等待! ??????祝你好运 【参考方案1】:

这是一个有趣的谜题。

解决问题的“特殊酱料”就在这一行:

.flatMap  
    Observable.combineLatest($0.map  
        Observable.combineLatest(
            Observable.just($0.0), 
            URLSession.shared.rx.data(request: $0.1)
                .materialize()
        ) 
    ) 

该行之前的map 创建一个Observable<[(URL, URLRequest)]>,而相关行将其转换为Observable<[(URL, Event<Data>)]>

该行通过以下方式执行此操作:

    设置网络调用以创建Observable<Data> 将其具体化以创建 Observable<Event<Data>>(这样做是为了使一次下载中的错误不会关闭整个流。) 将 URL 重新提升到 Observable 中,这给了我们一个 Observable<URL> 结合第 2 步和第 3 步中的 observables,生成 Observable<(URL, Event<Data>)>。 映射每个数组元素以产生[Observable<(URL, Event<Data>)>] 组合该数组中的 observables 最终生成Observable<[(URL, Event<Data>)]>

这里是代码

// manipulatedUsers is for the code you commented out.
// users: Observable<Users>
let users = self.dataManager.getUsers()
    .map(manipulatedUsers) // manipulatedUsers(_ users: Users) -> Users
    .asObservable()
    .share(replay: 1)

// this chain is for handling the users object. You left it blank in your code so I did too.
users
    .observeOn(MainScheduler.instance)
    .subscribe(onNext:  users in

    )
    .disposed(by: disposeBag)

// This navigates through the users structure and downloads the images.
// images: Observable<(URL, Event<Data>)>
let images = users.map  $0.categories ?? [] 
    .map  $0.flatMap  $0.profiles ?? []  
    .map  $0.compactMap  $0.thumbnail  
    .map  $0.compactMap  URL(string: $0)  
    .map  $0.map  ($0, URLRequest(url: $0))  
    .flatMap  
        Observable.combineLatest($0.map  
            Observable.combineLatest(
                Observable.just($0.0), 
                URLSession.shared.rx.data(request: $0.1)
                    .materialize()
            ) 
        ) 
    
    .flatMap  Observable.from($0) 
    .share(replay: 1)

// this chain filters out the errors and saves the successful downloads.
images
    .filter  $0.1.element != nil 
    .map  ($0.0, $0.1.element!) 
    .map  ($0.0, UIImage(data: $0.1)!) 
    .observeOn(MainScheduler.instance)
    .bind(onNext:  url, image in
        try? Disk.save(image, to: .caches, as: url.lastPathComponent)
        return // need two lines here because this needs to return Void, not Void?
    )
    .disposed(by: disposeBag)

// this chain handles the download errors if you want to.
images
    .filter  $0.1.error != nil 
    .bind(onNext:  url, error in
        print("failed to download \(url) because of \(error)")
    )
    .disposed(by: disposeBag)

【讨论】:

感谢这个伟大的解决方案。只是询问是否有任何有效的方法可以将用户和图像可观察对象结合起来并从用户返回结果,并且只有从图像到用户可观察的错误? 你的意思是你想要Observable&lt;(Users, [UIImage])&gt;?这听起来不太有用,但可以做到。我认为更有用的输出是Observable&lt;[(Profile, UIImage)]&gt;,也可以这样做。但是,您需要更多地关注您想要的输出。您要达到的输出是什么?这是为了填充表格视图单元格(在这种情况下,每个单元格都应该下载自己的图像)还是其他什么?对于这些更模糊的问题,RxSwift 松弛可能是一个更好的地方。 是的,输出是填充表格视图单元格。对于此输出,仅需要用户结果,并且只对图像下载是否发生错误感兴趣,因此可以跳过用户的结果。否则你的解决方案就足够了。 :) 也许您可以在 slack 或直接消息中分享这个替代解决方案。 当我不知道问题时,我无法分享解决方案。在 RxSwift slack 上联系我,我们可以谈谈。

以上是关于地图中的 RxSwift 多个可观察对象的主要内容,如果未能解决你的问题,请参考以下文章

等待多个可观察请求完成使用 RXSwift

RxSwift:结合不同类型的可观察对象和映射结果

RxSwift 将可观察对象数组与对象数组结合

RxSwift:嵌套可观察对象上的 FlatMap

RxSwift:返回一个带有错误的新可观察对象

来自数组的 RxSwift 可观察对象序列