Debounce vs. Throttle in Combine
Posted by scotteg on August 15, 2019
Introduction
The debounce
and throttle
operators exist in most reactive implementations, such as RxSwift, yet they continue to elicit confusion about what each one does and how to use them effectively.
Setup
The examples will use the following setup code:
// 1
let values = "abcdefghijklmnopqrstuvwxyz"
.map { String(describing: $0) }
.reduce([String]()) { values, next in
let new = (values.last ?? "") + next
return values + [new]
}
// 2
let subject = PassthroughSubject<(Double, String), Never>()
// 3
values.enumerated().forEach { i, value in
let delay = TimeInterval(i) / 10.0
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
subject.send((delay, value))
}
}
- Create an array of incremental values
["a", "ab", "abc"...]
. - Create a passthrough subject to send values at specified time intervals.
- Iterate over the array, sending each value to the passthrough subject after an increasing delay.
This code results in each value being sent at a rate of one character every tenth of a second.
debounce
Essentially, debounce
will pause for a specified time after each value is received, and then at the end of that pause it will send the last value through.
The following code creates a subscription to the subject
created in the setup code:
let debounceSubscription = subject
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.sink(receiveValue: { print($0.1) })
This code will produce the following output:
Each sequential value triggers a half-second pause, during which time nothing is sent. After the last value is sent (a...z
), the half-second pause completes and that last value is sent and printed.
throttle
Conversely, throttle
does not pause after receiving values. Instead, it waits for the specified interval repeatedly, and at the end of each interval it will send either the first or the last value depending on what is passed for its latest
parameter.
When true
is passed for latest
, at the end of each interval it will send the last value received during the time interval.
The following code creates a subscription to the subject
created in the setup code:
let throttleLatestSubscription = subject
.throttle(for: .milliseconds(500), scheduler: DispatchQueue.main, latest: true)
.sink(receiveValue: { print($0.1) })
This code will produce the following output:
After each half-second interval, throttle
sends the latest value it received during that interval.
If latest
is false
, throttle
will send the first value received during that time interval.
The following code creates a subscription to the subject
created in the setup code:
let throttleFirstSubscription = subject
.throttle(for: .milliseconds(500), scheduler: DispatchQueue.main, latest: false)
.sink(receiveValue: { print($0.1) })
This code will produce the following output:
After each half-second interval, throttle
sends the first value it received during that interval.
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