swiftrx-swift

Understanding difference between zip and combineLatest with RxSwift for sequence parameters


I have used two functions. First combineLatest:

        enum Weather {
            case cloudy
            case sunny
        }
        let left: Observable<Weather> = Observable.of(.sunny, .cloudy, .cloudy, .sunny)
        let right = Observable.of("Lisbon", "Copenhagen", "London", "Madrid", "Vienna")
        let observable = Observable.combineLatest(left, right) { weather, city in
            return "It's \(weather) in \(city)"
        }
        _ = observable.subscribe(onNext: { value in
            print(value)
        })

with output:

It's sunny in Lisbon
It's cloudy in Lisbon
It's cloudy in Copenhagen
It's cloudy in Copenhagen
It's cloudy in London
It's sunny in London
It's sunny in Madrid
It's sunny in Vienna

and zip:

        enum Weather {
            case cloudy
            case sunny
        }
        let left: Observable<Weather> = Observable.of(.sunny, .cloudy, .cloudy, .sunny)
        let right = Observable.of("Lisbon", "Copenhagen", "London", "Madrid", "Vienna")
        let observable = Observable.zip(left, right) { weather, city in
            return "It's \(weather) in \(city)"
        }
        _ = observable.subscribe(onNext: { value in
            print(value)
        })

with output:

It's sunny in Lisbon
It's cloudy in Copenhagen
It's cloudy in London
It's sunny in Madrid

How does it work? I understand that zip just map values one to one, and if there is no value for same index, it just skip it. But how to understand combineLatest?


Solution

  • combineLatest is a bit easier to understand (and more useful!) if the two observables don't emit elements immediately. An easier-to-understand example (which I encourage you to try) is:

    Observable.combineLatest(someUISwitch.rx.isOn, someUITextField.rx.text) { isOn, text in
        return "Text: \(text ?? ""), Switch is on: \(isOn)"
    }.subscribe(onNext: { value in
        print(value)
    }).disposed(by: disposeBag)
    

    Try adding a UISwitch and a UITextField into your UI. Now try entering some text into the text field, and also try switching the switch. You will see that whenever one of the two changes, that is, when either observable emits a new value, their combined value (combined by the closure passed to combineLatest) gets emitted by the combined observable.

    More generally, the observable produced by combineLatest emits its first value when all the observables first emits a value, and subsequent values are emitted when any of the observables emits a value. Whenever it emits a value, the value it emits is computed by combining all the latest values that all the observables have emitted. Hence "combine latest".

    For your Observable.of example, the values are all emitted immediately, but combineLatest behaves as if left and right emits values in an interlaced order, and left goes first. I've tried to add annotations to the output you got. See if this helps your understanding.

        (left emits sunny)
        (right emits Lisbon)
    It's sunny in Lisbon (the first values emitted by both observables)
        (left emits cloudy)
    It's cloudy in Lisbon
        (right emits Copenhagen)
    It's cloudy in Copenhagen
        (left emits 2nd cloudy)
    It's cloudy in Copenhagen
        (right emits London)
    It's cloudy in London
        (left emits sunny)
    It's sunny in London
        (right emits Madrid)
    It's sunny in Madrid
        (right emits Vienna, since left ran out)
    It's sunny in Vienna
    

    Also, don't forget there's RxMarbles that you can play with.