Search code examples
swiftcombine

What is the reason to store subscription into a subscriptions set?


Combine subscription sample code snippet all store the resulting subscription into the subscriptions set

private var subscriptions = Set<AnyCancellable>()

Why do we need to do it?

future
  .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) }) 
  .store(in: &subscriptions)

Solution

  • We usually want to store the subscription somewhere, to keep the subscription alive. We often want to keep several subscriptions alive until the enclosing object is destroyed, so it's convenient to store all the subscriptions in a single container.

    However, the container does not have to be a Set! It can be (and usually should be) an Array.

    Cancellable provides two store(in:) methods:

    extension Cancellable {
        public func store<C>(in collection: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable
    
        public func store(in set: inout Set<AnyCancellable>)
    }
    

    (Array conforms to RangeReplaceableCollection, but Set does not, so it needs its own method.)

    You have found the one that stores into a Set. But do you need the behavior of a Set? The only reason to store your subscriptions in a Set is if you need to efficiently remove a single subscription from the set, and the set may be large. Otherwise, just use an Array, like this:

    class MyObject {
    
        private var tickets = [AnyCancellable]()
    
        ...
            future
                .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) }) 
                .store(in: &tickets)
    

    I think the reason you often see code that uses a Set<AnyCancellable> is because most Swift programmers are much more familiar with Set and Array than with RangeReplaceableCollection. When Xcode offers to autocomplete store to take either a Set or a RangeReplaceableCollection, you're going to pick the Set version if you don't know that an Array is a RangeReplaceableCollection.