Implement a Debouncer in Swift

Learn to give your method calls some space to breathe between execution

What is Debouncing a Method Call?

Debouncing a method call ensures that the method is not executed too frequently.

For example: if you see a basic implementation of a search view in any application it does not make an HTTP call to the back end for every character you type. This would result in many HTTP calls and most of them would be unnecessary.

Instead, we use something called a Debouncer, which calls a particular method only after a given interval of time. For the search example, if we have a time limit of 0.3 seconds then the HTTP call to get the search results will be made when the user has not typed anything for 0.3 seconds. If the user continues to type without pausing for 0.3 seconds the time interval is renewed every time a key is pressed.

Implementing a Custom Debouncer

public class Debouncer {

    private let timeInterval: TimeInterval
    private var timer: Timer?

    typealias Handler = () -> Void
    var handler: Handler?

    init(timeInterval: TimeInterval) {
        self.timeInterval = timeInterval
    }

    public func renewInterval() {
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { [weak self] (timer) in
            self?.timeIntervalDidFinish(for: timer)
        })
    }

    @objc private func timeIntervalDidFinish(for timer: Timer) {
        guard timer.isValid else {
            return
        }

        handler?()
        handler = nil
    }

}

Let's understand how this has been implemented!

Line 3: This is the time interval of the debouncer. If no action has been taken for this time interval then the method will be called

Line 4: We are using a timer to make sure method is not called before the time interval is finished

Line 6,7: This is a handler closure which will be called when the time interval has ended. Inside this closure, we can implement the code to run, or the code to be debounced.

Line 9: This is a custom init which takes time interval as parameter. We cannot have a debouncer instance without a time interval

Line 13: The renew interval method is used to renewal the debounce time interval. If the user is pressing ten keys without pausing the interval will be renewed ten times to ensure we’re checking for time interval from the last user action. Every time this method is called we’re creating a new Timer which will call a function after time interval is finished.

Line 20: This method is called when the Timer completes the time interval. In return, we will call the handler which is responsible for executing the debounce code.

Using the Debouncer

class ViewController: UIViewController {

    let debouncer = Debouncer(timeInterval: 0.3)

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.doSomething()

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                self.doSomething()

                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                    self.doSomething()

                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                        self.doSomething()
                    }
                }
            }
        }
    }

    func doSomething() {

        debouncer.renewInterval()
        print("renew interval")
        debouncer.handler = {
            print("method executed")
        }
    }
}

Line 3: We’ve created an instance of Debouncer with a time interval of 0.3 seconds.

Line 25: doSomething is the method that we are planning to debounce. Every time interval is renewed it will print out renew interval and whenever the method is executed inside the handler it will print out method executed.

Line 5: We are enacting user actions in viewDidLoad using DispatchQueue
We call the doSomething() method three times after an interval of 0.1 seconds. The time interval is greater than 0.1 seconds so handler will not be executed instead debouncer will renew its interval three times.

Then, there is a pause of 0.4 seconds — greater than debouncer time interval so it will execute the handler. After that, Line 18 renews the interval once again and execute the handler after 0.3 seconds.

The output will look like this:

Wrapping Up

This was a basic implementation of Debouncer. There are many different methods to implement the same thing — they’re all correct if they meet your requirements.

You can find the sample project on Github.

Did you find this article valuable?

Support Samarth Paboowal by becoming a sponsor. Any amount is appreciated!