Home

Operators

Composing streams, and where the thread goes

Operators are publishers that wrap other publishers. map returns a Publishers.Map, which holds its upstream and transforms each value as it passes. Nothing runs until something subscribes.

let results = searchTerms
    .removeDuplicates()
    .flatMap { term in
        api.search(term)
            .catch { _ in Just([]) }
    }
    .receive(on: DispatchQueue.main)

Three things worth knowing about this chain:

  • flatMap keeps every inner publisher alive until it completes. For search-as-you-type that means overlapping requests and out-of-order results; switchToLatest cancels the previous one.
  • catch ends the stream it recovers. Once an error passes through, that upstream is finished — which is why the recovery is placed inside flatMap, on the inner publisher, not outside it.
  • receive(on:) moves delivery, not work. Whatever api.search does happens where it already happened; only the values after this point arrive on main.

Why these chapters are grouped this way

Combine has around a hundred operators and an alphabetical tour of them would be useless. They are grouped here by the question they answer, because in practice you know what you want and need the name.

Transforming — one stream in, one stream out, values changed on the way. map, compactMap, scan, flatMap, switchToLatest, and the difference between the last two, which is the most consequential choice in this book.

Combining — several streams in, one out. merge, combineLatest, zip, and the first-value-only operators. Choosing wrongly here produces a screen that updates too often or one that never updates at all, and the three behave very differently the moment one source is slower than the others.

Timing — debounce, throttle, delay, timeout, collect(.byTime:), and measureInterval. Everything here needs a scheduler, and the choice of scheduler changes the behaviour.

Failure — catch, retry, replaceError, mapError, setFailureType, and the discipline of deciding where in a chain an error is allowed to be fatal.

The rule underneath all of them

An error terminates the stream it reaches. Not the value — the stream. Once a failure passes a point in the chain, everything downstream of it receives a completion and the subscription is over. No later value can arrive, because there is nothing left to arrive on.

This is the single most important sentence about Combine operators, and it drives the placement of almost every error-handling operator you will write. A catch at the end of a pipeline recovers once and then the pipeline is dead; the same catch inside a flatMap recovers the inner publisher and leaves the outer stream alive to handle the next value.

Almost every “it worked the first time and then stopped” bug in Combine is that distinction.