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!


Operators

Operators are the objects that stand within a pipeline between the initial publisher at the extreme upstream end and the final subscriber at the extreme downstream end. They are generated by operator methods, and in fact it is common parlance to speak of the methods as if they are the operators.

For example, to generate a Publishers.Map operator you will usually use the .map method:

let url = URL(string:"https://www.apeth.com/pep/manny.jpg")
URLSession.shared.dataTaskPublisher(for: url!)
    .map {$0.data}
    .sink(receiveCompletion: { _ in }){ print($0) }
    .store(in:&self.storage)
// 12212 bytes

Thus it is natural to speak of .map as the operator, even though, behind the scenes, a Publishers.Map object is the real operator.

Nevertheless, it is perfectly possibly to instantiate a real operator object yourself and attach it manually to a publisher. To do so, you’ll call an initializer of this operator type; it will always have an upstream: parameter, which is the Publisher to which we want to attach this operator. The initializer needs this information for two reasons:

  • It needs to know what Publisher we’re going to be subscribing to.

  • It needs to know the Output and Failure types of that Publisher, so that this operator can be instantiated with the corresponding Input and Failure types.

For example, to make a Map operator that we can subscribe to a URLSession.DataTaskPublisher, we initialize the Map operator with an actual URLSession.DataTaskPublisher as its upstream, like this:

let url = URL(string:"https://www.apeth.com/pep/manny.jpg")
let pub = URLSession.shared.dataTaskPublisher(for: url!)
let map = Publishers.Map(upstream:pub) { $0.data }

In that code, map is a Publishers.Map object with Input type (data:Data, response:URLResponse) and Failure type URLError, and whose upstream is pub, the data task publisher.

As a result, we now have a pipeline all the way down to the map part — sort of. Perhaps I should say that what we have is a potential pipeline, because the Map object, while it now has a reference to the URLSession.DataTaskPublisher object, is not actually subscribed to it. Subscribing doesn’t happen until someone subscribes to the Map object itself! For example, let’s complete the pipeline in the usual way:

let url = URL(string:"https://www.apeth.com/pep/manny.jpg")
let pub = URLSession.shared.dataTaskPublisher(for: url!)
let map = Publishers.Map(upstream:pub) { $0.data }
map.sink(receiveCompletion: { _ in }){ print($0) }
    .store(in:&self.storage)
    // 12212 bytes

The sink method produces a Sink object which is a Subscriber, and subscribes it to the Map object. The Map object responds by sending receive(subscriber:) to its upstream, the URLSession.DataTaskPublisher object — and so now the pipeline starts working, the URLSession does some networking, and a value pops out the end of the pipeline.

That’s not a common thing to do, but it’s not unheard-of either. It’s useful to know that you can do it, and doing so demonstrates clearly what happens behind the scenes when you call an operator method on a publisher, and how the construction of a full pipeline really works.

The art of using the Combine framework consists largely of choosing and configuring operators to form the pipeline. You want to transform and channel signals from your publisher(s) so that the right signals, of the right type, in the right order, arrive at the subscriber at the end of the pipeline. The Combine framework provides a large panoply of operators to assist with just about every need you can think of.

NOTE: Many operators are named after familiar Sequence methods: map, filter, reduce, first, dropFirst, and so forth. This makes good metaphorical sense; the values emitted by a publisher do constitute a kind of sequence.


Table of Contents