Recipes for Combining Observables in RxSwift

Daniel Tartaglia
4 min readOct 28, 2018

Several operators exist to combine multiple observables into one. This document shows the basics of the various combining operators and shows some more advanced recipes using them.

Combine Latest

The combineLatest operator is used whenever you have two or more observables emitting values, and you want access to the latest value emitted from each of them whenever one of them emits a new value. It can be used, for example, to combine a username and password to make a login request.

func example(username: Observable<String>, password: Observable<String>) {
let credentials: Observable<(String, String)> = Observable.combineLatest(username, password)
//...
}

It also comes in handy when you have an array of Observables, and you want to convert them into an Observable that emits an array.

func example(imageObservables: [Observable<UIImage>]) {
let images: Observable<[UIImage]> = Observable.combineLatest(imageObservables)
//...
}

Sometimes, one of the Observables will emit several values before all of them have. The combineLatest operator will ignore those values until such time as all the constituents have emitted a value. If you don't want to miss out on any such values, then it's best to use startWith to provide defaults:

func example(pageNumber: Observable<Int>, searchTerm: Observable<String>) {
let pageAndTerm: Observable<(Int, String)> = Observable.combineLatest(pageNumber.startWith(1), searchTerm)
//...
}

On occasion you need to use a value emitted by an Observable to create a new Observable, but you want to tie the value with the new Observable. For this, you can use just to lift the value back up into an Observable and combine it:

func example(pageNumber: Observable<Int>) {
let pageAndItems: Observable<(Int, [Item])> = pageNumber
.flatMapLatest { page in
Observable.combineLatest(Observable.just(page), getItems(forPage: page))
}
}

You could also do the above with an additional map:

func example(pageNumber: Observable<Int>) {
let pageAndItems: Observable<(Int, [Item])> = pageNumber
.flatMapLatest { page in
getItems(forPage: page)
.map { (page, $0) }
}
}

Zip

Then there are times where you want to fire off a bunch of Observables, then wait until they all emit a value. If you know they will all emit no more than one value, you could use combineLatest. However, it's probably more expressive of intent if you use zip.

func example(imageServerRequests requests: [Single<Data>]) {
let images: Observable<[Data]> = Observable.zip(requests)
//...
}

The zip operator also comes in handy if an Observable is emitting values too fast and you want to slow it down. Normally, from will emit all of the values in the array immediately upon subscription; by zipping it with an Observable that emits its values slowly, you can force from to slow down.

func example(imageURLs: [URL]) {
let values: Observable<URL> = Observable.zip(Observable.from(imageURLs), Observable.interval(3, scheduler: MainScheduler.instance), resultSelector: { $0 })
//...
}

Merge

There are times when you have data coming from several possible sources and you want to treat it all the same; this is especially true with Void Observables which act as action triggers. For this merge is a great choice.

func example(pullToRefresh: Observable<Void>, tapToRefresh: Observable<Void>) {
let refresh: Observable<Void> = Observable.merge(pullToRefresh, tapToRefresh)
// ...
}

Another common use of merge is when you have Observables that perform different actions on the same (usually) piece of state. In these cases you don't exactly want to combine the emissions, rather you need them to come through one at a time, but you need to distinguish between them.

func example(startOver: Observable<Void>, nextPage: Observable<Void>) {
enum Action {
case startOver
case nextPage
}
let action: Observable<Action> = Observable.merge(startOver.map { .startOver }, nextPage.map { .nextPage })
}

WithLatestFrom

Sometimes you want to combine observables, but you only want an event when one of them fires, not both. In that case, you use withLatestFrom(_:_:).

let selectedFamilyMember = input.selectedIndex
.withLatestFrom(familyMembers) { $1[$0.row] }

There is also a withLatestFrom(_:) that will give you the latest emission of the second observable and ignore the emission from the first observable. That version is great for triggers and is the one that is most commonly used.

Concat

Lastly, there are times when you want to fire off a bunch of Observables in sequence rather than all at once, but you still want to collect up all the results. The concat operator is good for that.

let results = Observable.concat(networkRequests)

This will cause all the Observables in the networkRequests array to be subscribed to one at a time. The next one in the sequence won’t be subscribed to until the previous one completes. Make sure that the inner observables emit completion events or it will never finish.

Another great source to get a sence of how these operators work is in the Combining_Operators playground. Always be sure to check the official docs when you have any questions about an operator. Every one of them is well documented both as a doc comment and within a playground.

I hope this little tour of the combining operators helps out. If I got anything wrong, or your have better, or other, examples then by all means send me a DM on slack, twitter or reddit (danielt1263) or just leave a comment here.

--

--

Daniel Tartaglia

I started programming as a hobby in the late ’70s and professionally in the late ’90s. I’ve been writing iOS apps since 2010 and using RxSwift since 2015.