I have a requirement to request an Article
.
Each Article
contains an array of ArticleAsset
which has various props on I need when rendering an entire article.
I do not know ahead of time how many assets exist on an article, so I must request the article and then using the assets
prop dispatch X amount of request to return the value of each ArticleAsset
.
At that point I should then return both the article and the array of results for my asset fetches.
For simplicity imagine in this case each asset returns an Int
. So I start with this -
Article
> [Article]
I would expect to end up with an tuple of the following shape (article: Article, assets: [Int])
I have attempted to recreate this as the below playground, but have been completely unsuccessful and am a little stuck.
I understand how to chain a fixed number of requests, using flatMapLatest
etc but in this case I do not know the number of requests. I'm thinking I should map each ArticleAsset
and return an array of Observables
however I start to get very fuzzy on where to go next.
Any help would be much appreciated please and thank you.
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)
Kudos in making a compilable playground! That makes things a lot easier. What you want to do here is combine observables. In my article you will see that there are a lot of ways to do that. I think for this use-case the zip
operator is best.
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.
})
When fetchArticle()
emits a value, the flatMap
s closure will be called and it will call getArticleAsset(asset:)
for each asset, wait until they are all done, combine them into a single Observable array (the articles
object) and then combine that with the .just(article)
observable.
Warning though, if any one asset request fails, the entire chain fails. If you don't want that, you will have to take care of that inside the { getArticleAsset(asset: $0) }
block. (Maybe emit a nil or missingAsset
asset instead.)