Цепочка запросов в RxSwift с использованием первого запроса для настройки дальнейших запросов, а затем возврат всех по завершении

У меня есть требование запросить Article.

Каждый Article содержит массив ArticleAsset, который имеет различные свойства, которые мне нужны при рендеринге всей статьи.

Я не знаю заранее, сколько ресурсов существует в статье, поэтому я должен запросить статью, а затем с помощью assets prop отправить X количество запроса, чтобы вернуть значение каждого ArticleAsset.

На этом этапе я должен вернуть и статью, и массив результатов для моих выборок активов.

Для простоты представьте, что в этом случае каждый актив возвращает Int. Итак, я начну с этого -

Article > [Article]

Я ожидал бы получить кортеж следующей формы (article: Article, assets: [Int])

Я попытался воссоздать это как игровую площадку ниже, но безуспешно и немного застрял.

Я понимаю, как связать фиксированное количество запросов, используя flatMapLatest и т. Д., Но в этом случае я не знаю количество запросов. Я думаю, что мне нужно сопоставить каждый ArticleAsset и вернуть массив Observables, но я начинаю очень нечетко понимать, куда идти дальше.

Любая помощь будет принята с благодарностью, пожалуйста, и спасибо.

import UIKit
import RxSwift

private let disposeBag = DisposeBag()

struct Article {
       let id: UUID = UUID()
       var assets: [ArticleAsset]
   }

   struct ArticleAsset {
       let number: Int
   }

   let assets: [ArticleAsset] = Array(0...4).map { ArticleAsset(number: $0) }
   let article = Article(assets: assets)

   func fetchArticle() -> Observable<Article> {
       return Observable.of(article)
   }

   func getArticleAsset(asset: ArticleAsset) -> Observable<Int> {
       return .of(asset.number)
   }

   fetchArticle()
       .map { art in
           let assets = art.assets.map { getArticleAsset(asset: $0) }
           let resp = (article: art, assets: Observable.of(assets))
           return resp
   }.subscribe(onNext: { resp in

     // I would like my subscriber to receive (article: Article, assets: [Int])

   }).disposed(by: disposeBag)

person Harry Blue    schedule 19.11.2019    source источник


Ответы (2)


Престижность в создании собираемой детской площадки! Это значительно упрощает работу. Здесь вы хотите объединить наблюдаемые. В моей статье вы увидите, что есть много способов сделать это. Я думаю, что для этого варианта использования лучше всего подходит оператор zip.

let articleWithAssets = fetchArticle()
    .flatMap { (article) -> Observable<(article: Article, assets: [Int])> in
        let articles = Observable.zip(article.assets.map { getArticleAsset(asset: $0) })
        return Observable.zip(Observable.just(article), articles) { (article: $0, assets: $1) }
    }

articleWithAssets
    .subscribe(onNext: { resp in
        // here `resp` is of type `(article: Article, assets: [Int])` as requested.
    })

Когда fetchArticle() испускает значение, будет вызвано закрытие flatMaps, и оно вызовет getArticleAsset(asset:) для каждого актива, дождется, пока все они будут выполнены, объединит их в один массив Observable (объект articles), а затем объединит его с наблюдаемым .just(article).

Однако предупреждение: если какой-либо один запрос ресурса завершится неудачей, вся цепочка завершится неудачей. Если вы этого не хотите, вам придется позаботиться об этом внутри блока { getArticleAsset(asset: $0) }. (Может быть, вместо этого выпустить ноль или missingAsset актив.)

person Daniel T.    schedule 20.11.2019

Rx имеет несколько операторов сжатия. flatMapLatest - это не то, что вы хотите здесь, потому что оно даст вам результаты только из последней внутренней наблюдаемой. Вам действительно нужно объединить все потоки ArticleAsset и продолжить, только когда они все будут готовы. Итак, вы хотите merge все ваши запросы ArticleAsset, а затем reduce их в массив ArticleAsset.

fetchArticle()
  .flatMap { article in 
     let allAssetRequests = article.assets.map {
      getArticleAsset(asset: article)
     }
     return Observable
       .merge(allAssetRequests)
       .reduce([ArticleAsset]()) { array, asset in
          //Combine here
       }
  }

Вы можете изменить здесь reduce, чтобы преобразовать его в кортеж: (Article, [ArticleAsset]), и тогда у вас будет окончательная форма потока, который вы ищете.

person Josh Homann    schedule 19.11.2019