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!
As I explained in the previous section, we need to store our subscriber in persistent storage so that the pipeline lives long enough for the publisher to have a chance to publish at some time in the future, whenever that may be. After that, however, a publisher that only publishes once — a one-shot publisher — has done its work, and can be permitted to go out of existence.
In a situation like that, we only need our subscriber to persist long enough to receive a value or a completion (or both). It is a one-shot subscriber. I’m going to show you a little trick that I use to make a one-shot subscriber, without having to go to all the trouble of preparing an instance property and calling store(in:).
Let’s start with our usual one-shot publisher, a data task publisher. This is the same code you’re already so familiar with:
let url = URL(string: "https://www.apeth.com/pep/manny.jpg")!
let pub: AnyPublisher<UIImage?, Never> =
URLSession.shared.dataTaskPublisher(for: url)
.map {$0.data}
.replaceError(with: Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
Now comes the interesting part. Watch closely:
var cancellable: AnyCancellable? // 1
cancellable = pub.sink(receiveCompletion: {_ in // 2
withExtendedLifetime(cancellable) { _ in } // 3
}) { image in
self.imageView.image = image
}
Do you see what I did there? Perhaps not, so I’ll explain it:
First, I declare a local AnyCancellable variable; for reasons having to do with the rules of Swift syntax, this needs to be an Optional.
Then, I create my subscriber and set my AnyCancellable variable to that subscriber. Again, for reasons having to do with the rules of Swift syntax, my subscriber needs to be a Sink.
Finally, in the subscriber itself, I call withExtendedLifetime(cancellable) inside the asynchronous completion function. This keeps cancellable and the whole pipeline alive long enough for a value to arrive from the subscriber. At that point, the completion handler is called, the withExtendedLifetime anonymous function is called (and does nothing), and cancellable is released, permitting the pipeline to go out of existence in good order.
This use of
withExtendedLifetimewas suggested to me by reader@Helam24.