In the previous episode, you learned about publishers and subscribers. Combine creates a subscription when a subscriber is attached to a publisher. In the setupNotificationHandling() method of the RootViewModel class, the subscription is returned to the view model as an AnyCancellable instance. The view model holds on to the AnyCancellable instance to prevent the subscription from terminating early. We covered this in the previous episode.
Managing Subscriptions
The view model uses a property to hold on to the subscription. This is fine, but, as you can imagine, this gets old very quickly. Should you declare a property for each subscription? The answer is no. There is a more convenient pattern that closely resembles the pattern you find in RxSwift.
The idea is simple. We create a collection to which subscriptions can be added. The view model no longer keeps a reference to each subscription. Instead it holds a reference to the collection of subscriptions. When the view model is about to deallocated, the collection of subscriptions is also deallocated. That in turn cancels the subscriptions that are stored in the collection. Let me show you how this works.
We declare a property, subscriptions, of type Set<AnyCancellable> and assign an empty set to it. The view model uses the subscriptions property to manage the subscriptions it creates.
private var subscriptions: Set<AnyCancellable> = []
You could manually add the subscription to the set of subscriptions, but the Combine framework has a convenient API for doing exactly that.
The AnyCancellable class conforms to the Cancellable protocol. The Cancellable protocol defines two store(in:) methods to add an AnyCancellable instance to a collection. The store(in:) methods each define an in-out parameter, the collection to which the AnyCancellable instance should be added. Notice that we prefix the subscriptions variable with an ampersand to make it explicit that we are passing subscriptions as an argument to an in-out parameter.
private var subscriptions: Set<AnyCancellable> = []
private func setupNotificationHandling() {
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [weak self] _ in
self?.requestLocation()
}
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { notification in
print("did receive notification")
}.store(in: &subscriptions)
}
The idea is similar to what we discussed earlier. When the view model is deallocated, the set and the subscriptions it contains are also deallocated. When that happens, each AnyCancellable instance calls cancel() to cancel the subscription it references.
AnyCancellable and Cancellable
Adding an AnyCancellable instance to a set of AnyCancellable instances is convenient and it is a pattern you use often when working with the Combine framework. Before we move on, I would like to take a closer look at subscriptions and subscription cancellation.
The sink(_:) method returns an AnyCancellable instance. You learned in the previous episode that AnyCancellable is a class. Apple's documentation is a bit cryptic, though. It simply reads:
A type-erasing cancellable object that executes a provided closure when canceled.
What I want you to remember is type-erasing. We explore type erasure in detail later in this series because it is a concept you need to understand if you want to effectively use Combine. What I want you to understand for now is that an AnyCancellable instance hides the subscription that is created when the subscriber is attached to the publisher. The AnyCancellable instance simply provides an interface for cancelling the subscription without providing direct access to the subscription. Why that is important becomes clear later in this series. Don't worry about the details for now.
Terminology
You learned in this episode that, strictly speaking, an AnyCancellable instance isn't the same as a subscription. For convenience and simplicity, I often use AnyCancellable instance and subscription interchangeably. You learn more about both in the remainder of this series.