Search code examples
swiftsubscriptioncombine

Store subscription from custom Combine subscriber?


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.


Solution

  • 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.