Back

Pause and Resume a Subscription in Combine

Posted by scotteg on August 13, 2019


Creating and Pausing a Subscription

Publishers and subscribers have this basic interplay:

It’s when the publisher sends values to the subscriber, or more specifically, the subscriber receives those values, that it can adjust its demand for future values (additively).

Let’s start this example off by creating a custom subscriber:

final class IntSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    
    // 1
    var subscription: Subscription?
    
    func receive(subscription: Subscription) {
        // 2
        subscription.request(.max(2))
        self.subscription = subscription
    }
    
    func receive(_ input: Int) -> Subscribers.Demand {
        print("Received value", input)
        switch input {
        case 1:
            return .max(1) // 3
        default:
            return .none // 4
        }
    }
    
    func receive(completion: Subscribers.Completion<Never>) {
        print("Received completion", completion)
    }
}
  1. An optional subscription property is defined, which will be used to store the subscription
  2. The subscriber requests up to a max of 2 values when it receives the subscription, and it stores the subscription

In receive(_:) this subscriber implementation is dynamically adjusting its demand. The new demand will be the previous demand plus the demand returned from receive(_:), so…

  1. The new max is 3 (original max of 2 + new max of 1).
  2. max will remain 3 (previous 3 + none)

Continuing the example:

// 5
let subscriber = IntSubscriber()

// 6
let subject = PassthroughSubject<Int, Never>()

subject.subscribe(subscriber)

// 7
subject.send(1)
subject.send(2)
subject.send(3)
subject.send(4)
subject.send(5)
  1. Create an instance of IntSubscriber.
  2. Create a passthrough subject and subscribe to it.
  3. Sends some values on the subject.

This will print:

Received value 1
Received value 2
Received value 3

The first value the subscriber receives is 1, and it returns .max(2), so the new max is 3 (original max of 2 + new max of 1). Then for the remaining values it receives, it returns a demand of .none, so the the demand remains 3.

In order to see all publishing events, you can use print() in the subscription:

subject
    .print()
    .subscribe(subscriber)

Now this will be printed out:

receive subscription: (PassthroughSubject)
request max: (2)
receive value: (1)
Received value 1
request max: (1) (synchronous)
receive value: (2)
Received value 2
receive value: (3)
Received value 3

Notice the publisher has not completed, and the subscription has not been canceled. The subscription is paused.

Resuming a Publisher

Continuing with the previous example, I’ll comment out the print() and add the following code to the example:

// 8
subscriber.subscription?.request(.max(2))

// 9
subject.send(6)
subject.send(7)
subject.send(8)
  1. Call request(_:) on the subscription, setting a new max of 2.
  2. Send some more values.

This will print:

Received value 1
Received value 2
Received value 3
Received value 6
Received value 7

The subscription has been resumed, and the new max is fulfilled.

There is one caveat here though. If the publisher completes before the subscription is resumed, it will not receive any more values or the completion event (i.e., the completion event is not replayed).

I’ll adjust the example to demonstrate this by uncommenting the print() and then inserting a send(completion: .finished) before the request(_:):

subject.send(completion: .finished)
print("--")
// 8
subscriber.subscription?.request(.max(2))

This will print:

receive subscription: (PassthroughSubject)
request max: (2)
receive value: (1)
Received value 1
request max: (1) (synchronous)
receive value: (2)
Received value 2
receive value: (3)
Received value 3
receive finished
Received completion finished
--
request max: (2)

To Learn More

If you’d like to learn more about Combine, check out this book I co-authored:

Combine: Asynchronous Programming with Swift

It’s packed with coverage of Combine concepts, hands-on exercises in playgrounds, and complete iOS app projects. Everything you need to transform yourself from novice to expert with Combine — and have fun while doing it!


Back

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

© 2020 Scott Gardner