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:
- A subscriber requests to subscribe
- A publisher gives the subscriber a subscription
- A subscriber requests values
- A publisher sends values
- A publisher sends a completion
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)
}
}
- An optional
subscription
property is defined, which will be used to store the subscription - 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…
- The new
max
is3
(originalmax
of2
+ newmax
of1
). max
will remain3
(previous3
+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)
- Create an instance of
IntSubscriber
. - Create a passthrough subject and subscribe to it.
- 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)
- Call
request(_:)
on the subscription, setting a new max of2
. - 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!
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
© 2020 Scott Gardner