RxSwift defines a number of operators to combine two or more observables into a single observable. Each of these operators serves a specific purpose. In this episode, I show you how to use the zip operator to combine observables.

Combining Observables with RxSwift's Zip Operator

Fire up Xcode and create a playground by choosing the Blank template from the iOS section.

Combining Observables with RxSwift's Zip Operator

Clear the contents of the playground and add an import statement for RxSwift and RxCocoa. We define two publish subjects. The first subject emits elements of type Int. The second subject emits elements of type String.

import RxSwift
import RxCocoa

let intSubject = PublishSubject<Int>()
let stringSubject = PublishSubject<String>()

We combine the subjects by applying the zip operator.

import RxSwift
import RxCocoa

let intSubject = PublishSubject<Int>()
let stringSubject = PublishSubject<String>()

Observable.zip(
    intSubject,
    stringSubject
)

We subscribe to the resulting observable, installing an onNext handler and an onCompleted handler. In the onNext handler, we print the value of the element the observable emits. In the onCompleted handler, we print a message to the console to indicate the observable terminated without an error.

import RxSwift
import RxCocoa

let intSubject = PublishSubject<Int>()
let stringSubject = PublishSubject<String>()

Observable.zip(
    intSubject,
    stringSubject
)
.subscribe(onNext: { values in
    print(values)
}, onCompleted: {
    print("completed")
})

The value of the element the observable emits is a tuple, combining the values of the elements of the upstream observables. In this example, the type of the zip observable is a tuple of type (Int, String).

Let's take a look at the elements the zip observable emits when the upstream observable start emitting elements. The first subject starts by emitting an integer. Notice that nothing is printed to the console. In other words, the zip observable doesn't emit an element. Even when the first subject emits a second element, the zip observable still doesn't emit an element. The zip observable emits its first element the moment the second subject emits an element. The first element the zip observable emits is a tuple consisting of the first element of the first subject and the first element of the second subject.

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("a")

// (1, "a")

If the first subject emits a third element, nothing happens.

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("a")
intSubject.onNext(3)

// (1, "a")

Let me explain what is happening. The zip operator creates an observable that republishes the elements of its upstream observables. As I explained earlier, the type of the observable is a tuple composed of the elements of the upstream observables, (Int, String) in this example.

What is unique about the zip operator is that it emits its first element when every upstream observable published an element. This makes sense if you understand how the zip operator works. It combines the elements of its upstream observables, respecting the order in which the elements are emitted. This means that the first element of the first observable is combined with the first element of the second observable, the second element of the first observable is combined with the second element of the second observable, and so on.

That explains why the zip observable emits a single tuple. Even though the first subject published three elements, the second subject published one element. Let me show you what happens if the second subject publishes another element.

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("a")
intSubject.onNext(3)
stringSubject.onNext("b")

// (1, "a")
// (2, "b")

The zip observable completes when both upstream observables emit a completion event or terminate with an error. Let's take a look at the example.

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("a")
intSubject.onNext(3)
stringSubject.onNext("b")
intSubject.onCompleted()
stringSubject.onCompleted()
intSubject.onNext(4)
stringSubject.onNext("c")

// (1, "a")
// (2, "b")
// completed

The output in the console changes if the second subject emits a completion event after emitting the letter c. This confirms that the zip observable completes when both upstream observables emit a completion event or terminate with an error.

intSubject.onNext(1)
intSubject.onNext(2)
stringSubject.onNext("a")
intSubject.onNext(3)
stringSubject.onNext("b")
intSubject.onCompleted()
intSubject.onNext(4)
stringSubject.onNext("c")
stringSubject.onCompleted()

// (1, "a")
// (2, "b")
// (3, "c")
// completed

Publishing the Previous Value and the Current Value

The zip operator can be used to create an observable that emits the previous element and the current element an observable emits. We pass the same observable to the zip operator twice, but apply the skip operator to the second observable. This simply means that the second observable doesn't emit the first element of the original observable.

import RxSwift
import RxCocoa

let numbers = Observable.from([1, 2, 3, 4, 5])

Observable.zip(
    numbers,
    numbers.skip(1)
)

The idea becomes clear if we print the values of the zip observable to the console. We also print a message to the console to indicate the observable terminated without an error.

import RxSwift
import RxCocoa

let numbers = Observable.from([1, 2, 3, 4, 5])

Observable.zip(
    numbers,
    numbers.skip(1)
)
.subscribe(onNext: { values in
    print(values)
}, onCompleted: {
    print("completed")
})
    
// (1, 2)
// (2, 3)
// (3, 4)
// (4, 5)
// completed

This works because each upstream observable needs to emit an element before the zip observable emits its first element.

Zipping More Observables

The RxSwift library defines variants of the zip operator the combine up to eight observables. If that isn't sufficient, you can also pass a collection of observables to the zip operator.

import RxSwift
import RxCocoa

let numbers = Observable.from([1, 2, 3, 4, 5])

Observable.zip([
    numbers,
    numbers.skip(1)
])
.subscribe(onNext: { values in
    print(values)
}, onCompleted: {
    print("completed")
})

The RxSwift library also defines variants of the zip operator that accept a closure, also known as a result selector, as their last argument to transform the elements the zip observable emits.

import RxSwift
import RxCocoa

let numbers = Observable.from([1, 2, 3, 4, 5])

Observable.zip(numbers, numbers.skip(1)) { number1, number2 in
    number1 + number2
}
.subscribe(onNext: { values in
    print(values)
}, onCompleted: {
    print("completed")
})

You can achieve the same result by combining the zip operator with the map operator.

import RxSwift
import RxCocoa

let numbers = Observable.from([1, 2, 3, 4, 5])

Observable.zip(
    numbers,
    numbers.skip(1)
)
.map { $0 + $1 }
.subscribe(onNext: { values in
    print(values)
}, onCompleted: {
    print("completed")
})

What's Next?

The zip operator is one of the many operators to combine two or more observables. It defines a clear relationship between the upstream observables, that is, it combines the elements of its upstream observables, respecting the order in which the elements are emitted.