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

3

8

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)
Lifeline answered 23/8, 2020 at 4:39 Comment(0)
C
11

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.

Colt answered 23/8, 2020 at 16:9 Comment(8)
Interesting but can you explain why it "usually should be" an array? Set's are not only for "efficiently removing a single items from large collections". They are also good for checking membership in O(1) time and preventing duplicates etc. We could also argue arrays are an ordered collection and we don't need order in this case? We don't need all the features of Sets but we don't need all the features of arrays either. I don't see a clear reason for using one over the other?Diglot
The common case is that we create some subscriptions and they last the lifetime of the containing object. So there is no need for checking membership or preventing duplicates, and thereforeSet has no benefit over Array as a collection of AnyCancellable and is less efficient. If you have a subscription whose lifetime you need to manage more closely, you probably have to store its AnyCancellable in a separate property anyway, so there’s no need to add it to the collection. I haven’t seen any real world scenario where Set<AnyCancellable> makes more sense than Array<AnyCancellable>.Colt
If you can point me to some real-world code where Set<AnyCancellable> makes more sense than Array<AnyCancellable>, please do. I would love to see a good use case.Colt
Thanks rob, I really can't think of any real code where Set makes more sense than array in a huge way excect that there is no inherent order here and having duplicate entries in cancellable seems meaningless so Sets seems to match the type of collection we are after, as we don't care about the order and we don't need duplicates. And I can't think of any case where Array makes more sense either to be honest. Just wondering why you were saying arrays are a better choice. Can you explain why you said set "is less efficient"?Diglot
I mean the implementation of Set.insert is inherently less efficient than the implementation of Array.append because Set.insert looks for duplicates, while Array.append does not. I don't think this efficiency makes a difference in a real-world programs. But real-world programs also don't have the risk of inserting an AnyCancellable twice either. The AnyCancellable is in my program's direct control only in the moment it is returned by sink and immediately passed to store(in:). The use of Set implies that I care about its guarantees, but I don't.Colt
I wonder why Set.insert should be hugely inefficient because Sets use hashing so any 'looking' for duplicates should be O(1). Benchmarks seem to show insertion is actually faster on Sets. See blog.stackademic.com/…Diglot
Why do you think it might be “hugely inefficient”?Colt
I don’t. But I thought you do 🙂. Because in your comments above you have said “Set.insert is inherently less efficient than the implementation of array.append” Also the use of any data structure doesn’t necessarily “imply” the the user cares about every guarantee that data structure might make. So I am still not clear why you think Arrays are a better choice in this caseDiglot
L
2

Cancellable is a protocol provided by Combine.

The subscription code (e.g. publisher, operators, subscriber call chain) return a Cancellable object to allow manual cancelation for a subscription and when the object is released from memory, it cancels the whole subscription and release its resources from memory.

To automate the releasing process, we can store multiple subscriptions in AnyCancellable collection, so when the collection is about to be deinitialized all subscriptions inside would be canceled and associated resources would be released.

AnyCancellable is a type-erased type to allow storing different cancelable types to be stored inside the same collection.

REFERENCE

store(in:) stores the type-erasing cancellable instance into a specified set.

Lifeline answered 23/8, 2020 at 4:39 Comment(0)
D
1

If you're only going to need one AnyCancellable, you can store it directly.

private var subscription: AnyCancellable?

If you need more than one, a Set is generally the way to go, because

  1. AnyCancellable conforms to Hashable.
  2. You don't need to store the same AnyCancellable multiple times.
Daniel answered 23/11, 2022 at 0:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.