1

According to the swift combine documentation, the proper way to connect a subscriber to a publisher is with the publishers subscribe<S>(S) protocol extension method. Then the publisher creates a subscription and passes that subscription on to the subscriber. All's well and good so far.

What I can't seem to figure out is how to gain access to that subscription and retain it in the calling code. How is sink() implemented such that it can return that subscription? Unless I'm mistaken, the subscription is responsible for retaining its subscriber, which means I can't store a reference to the subscription in the subscriber. And since Subscription isn't class bound, it can't be weak.

Plastech
  • 757
  • 6
  • 17
  • Interestingly, when I try to add a weak reference to a Subscription I get an error that it is "non-class-bound". But Apple's own documentation says: "Subscriptions are class constrained because a Subscription has identity, defined by the moment in time a particular subscriber attached to a publisher". I'm not sure what to make of that discrepancy – Plastech Nov 11 '21 at 00:18

2 Answers2

3

Here's an example of how sink might be implemented

import Combine
import Foundation

extension Publisher {
    func mockSink(
        receiveValue: @escaping (Output) -> Void,
        receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void) -> AnyCancellable {

            var result : AnyCancellable!
            let anySubscriber = AnySubscriber(
                receiveSubscription: { subscription in
                    subscription.request(.unlimited)
                    result = AnyCancellable({subscription.cancel()})
                },
                receiveValue: { (value : Output) in receiveValue(value); return .unlimited},
                receiveCompletion: receiveCompletion)

            subscribe(anySubscriber)
            return result
        }
}

var subscriptions = Set<AnyCancellable>()
["one", "two", "three"].publisher
    .mockSink(receiveValue: {debugPrint($0)}, receiveCompletion: {debugPrint($0)})
    .store(in: &subscriptions)

As you can see the ability to return an AnyCancellable arises from the magic of AnySubscriber which returns its subscription in a closure. AnySubscriber is a handy tool to have when implementing custom Publishers and it may be helpful if you want to implement your own Subscriber type. But basically a Subscriber only exposes the subscription if it is designed to.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
1

The cancellable retains the subscription (or possibly is the subscription. Remember Subscriptions are Cancellables.) So when you retain the Cancellable, you are retaining the subscription.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • That's the opposite of what I have been reading, and doesn't make a ton of sense given that the subscription needs to be able to send a completion on deinit. How could that ever happen if the subscriber is retaining the subscription? – Plastech Nov 11 '21 at 18:48