Skip to content

Instantly share code, notes, and snippets.

@danielt1263
Last active May 29, 2019 15:11
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielt1263/c46b0e8949bdb9e80f116ceecabf2cc2 to your computer and use it in GitHub Desktop.
Save danielt1263/c46b0e8949bdb9e80f116ceecabf2cc2 to your computer and use it in GitHub Desktop.

Recipes for Combining Observables in RxSwift

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 { pageNumber in
            Observable.combineLatest(Observable.just(pageNumber), getItems(forPage: pageNumber))
        }
     //...
}

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

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 comment on this gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment