Group Group Group Group Group Group Group Group Group

Received Cancel in Chapter 2

Hello!
I am actually following the Combine book, and it’s very interesting and detailed.
However there is a weird behavior that is differing from the one explained in the book.

If I use the store(in:) function to store a subscription inside the subscriptions Set, the book says that at the end of the playground, since subscriptions is deallocated, automatically all contained subscriptions will be cancelled.
To actually check this, the book suggests you to add the print() operatore before the sink.
And the print() works indeed fine, showing all the “received” messages that the subscribers receive on their subscriptions.
But on the book is also said that on the termination of the playground, since subscriptions is deallocated, the “received cancel” should be print by all the subscriptions contained in the set, if they where not completed.
However this does not actually happen, and I even tested it by using the “final” playground provided in chapter.
If instead I force a removeAll() on the subscriptions Set, it does actually work, and the “received cancel” is printed.

Is this a problem of playground? Or are actually the subscriptions cancelled, but the receive is not printed for this event?

Thanks.

2 Likes

@velin92 Thank you for posting this question. I was just wondering the same thing.

Yep. I have the same question about the lack of canceling without emptying the set.

@scotteg Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi @velin92, thanks for reporting this issue. Great catch!

Here’s what’s going on — there are two contributing factors:

  1. The subscriptions set was moved to the global scope of the playground after I had completed this chapter. Why? We were battling issues with beta versions of Xcode 11 playgrounds, and at one point we issued a beta release of the book that had a workaround that didn’t even use playgrounds. During those revisions the subscriptions set was moved to be defined globally vs. being defined in each example(_:action:) block. And we didn’t catch this issue in review.

  2. At the point in the book when it is indicated that you’ll see receive cancel printed, you have not yet sent the .finished completion event. So, if the subscriptions set was still defined in the example scope, the subscriptions would be canceled and you’d see receive cancel printed twice, as indicated in the text. In the next passage, you add sending a .finished event. So even if subscriptions was still defined in the example scope, you still would not see receive cancel, because the publisher is already terminated.

This revised code includes the local subscriptions set and it will print the receive cancel events to the console:

example(of: "CurrentValueSubject") {
  var subscriptions = Set<AnyCancellable>()
    
  // 1
  let subject = CurrentValueSubject<Int, Never>(0)
  
  // 2
  subject
    .print()
    .sink(receiveValue: { print($0) })
    .store(in: &subscriptions) // 3
  
  subject.send(1)
  subject.send(2)
  
  print(subject.value)
  subject.value = 3
  print(subject.value)
  
  subject
    .print()
    .sink(receiveValue: { print("Second subscription:", $0) })
    .store(in: &subscriptions)
}

I will update the chapter to define a local subscriptions set in this example to restore showing how it will cancel the contained subscriptions when deinitialized.

Thanks again!

cc: @yongwoo @mattwaller

I’m using Xcode V11.6 (11E708) and even though I’ve been using auto complete which does indeed have the .store(in: &subscriptions) methods, like the answer mentioned above, when the playground is run the console still doesn’t have the receive cancel output like in the book. Just an FYI.

Does this mean that there is still a Set<AnyCancellable> with values in it located somewhere in a global scope of the playground?

Edit: using a print(subscriptions) at the end of the playground file at this point returned an array of [Combine.AnyCancellable, Combine.AnyCancellable] so I guess that’s a yes.