This is Understanding Combine, written by Matt Neuburg. It is a work in progress. 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 editions are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. Thank you!


Wrappers

Wrappers
By wrappers I mean those operators that wrap an operator or pipeline in a type-erased form.

.eraseToAnyPublisher() collapses the generic complexity that results from chaining operators to a single type, AnyPublisher, generically typed by the Output and Failure types of the last operator in the chain. Use it whenever you need to declare the type of a pipeline chain; it will make your life a lot simpler. You’ll have a type that you can read and write and reason about, and if you change any of the operators in the chain, you won’t be changing the erased type (as long as you don’t change the Output and Failure types of the last operator). I gave an example earlier.

.makeConnectable (Publishers.MakeConnectable) wraps the pipeline in a ConnectablePublisher struct. This makes the pipeline behave like a timer publisher: instead of starting automatically when it’s subscribed to, it doesn’t start until you explicitly say connect to it (or if you’ve attached an .autoconnect operator to it). For example:

let pub = URLSession.shared.dataTaskPublisher(for: url)
    .receive(on: DispatchQueue.main)
    .map {$0.data}
    .replaceError(with: Data())
    .compactMap { UIImage(data:$0) }
    .eraseToAnyPublisher()
    .makeConnectable()

Now pub is a ConnectablePublisher wrapping an AnyPublisher whose types are UIImage and Never. Let’s suppose we now come along and subscribe to pub:

pub.sink {print($0)}
    .store(in:&self.storage)

Nothing happens. And nothing is going to happen until we say pub.connect(). When we do say pub.connect(), a Cancellable object will be returned. It’s a good idea to treat this like the AnyCancellable produced by .sink and .assign, namely, to call store(in:) on it, so that it doesn’t go out of scope and automatically cancel the pipeline before it has a chance to get started:

pub.connect()
    .store(in: &self.storage)

Once we do that, the pipeline will begin to operate, and messages will flow down into our .sink.


Table of Contents