This is Understanding Combine, written by Matt Neuburg. Corrections and suggestions are greatly appreciated (you can comment here). So are donations; please consider keeping me going by funding this work at http://www.paypal.me/mattneub. Or buy my books: the current (and final) editions are iOS 15 Programming Fundamentals with Swift and Programming iOS 14. Thank you!


Merge

.merge(with:) takes a publisher as a parameter; it is also applied to a publisher (obviously). Both publishers must have the same Output and Failure generic types. Now there are effectively two upstream publishers. When either of those upstream publishers produces a value, this operator passes that value downstream. The two streams of values are interleaved.

This goes on until both upstream publishers have sent a .finished completion. If either publisher sends a .failure completion, this operator cancels the other publisher and sends the failure on downstream.

Here’s a toy example:

func makeTimer() -> AnyPublisher<Int,Never> {
    Timer.publish(every: 1, on: .main, in: .common)
        .autoconnect()
        .scan(0) {i,_ in i+1}
        .eraseToAnyPublisher()
}
let merged = makeTimer().merge(with:makeTimer())

Now merged is a publisher that produces this output:

1
1
[one second pause]
2
2
[one second pause]
...

There are actually two forms of .merge(with:). If both publishers are of the very same type — two Timer.TimerPublishers, or two Publisher.Sequences, or whatever — that is a Publishers.MergeMany. If they are of different types (but with the same Output and Failure types), that is a Publishers.Merge. However, you won’t normally be conscious of this difference, and in any case you could turn the latter into the former by type-erasing both publishers with .eraseToAnyPublisher.

As a convenience, operators are provided for joining three, four, five, six, seven, or eight publishers. The syntax for forming all of them is the same: you say .merge(with:) followed by a comma-separated list of publishers. Behind the scenes, these are actually different operators — Publishers.Merge3, Publishers.Merge4, and so on through Publishers.Merge8. But again, you won’t normally be conscious of that fact.

And indeed those operators really are just convenience operators, as they do nothing you couldn’t have done for yourself by a repeated application of a simple .merge(with:). Just to prove it, I’ll create a five-way merge by looping:

func makeTimer() -> AnyPublisher<Int,Never> {
    Timer.publish(every: 1, on: .main, in: .common)
        .autoconnect()
        .scan(Int.random(in:1...10)) {i,_ in i+1}
        .eraseToAnyPublisher()
}
let arr = (1...5).map {_ in makeTimer()}
let merged = arr.dropFirst().reduce(into: arr[0].eraseToAnyPublisher()) {
    $0 = $0.merge(with: $1).eraseToAnyPublisher()
}

After that, merged is a Publishers.MergeMany that produces five sequences of integers, interleaved.


Table of Contents