Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Swift 3 debounce & throttle
//
// debounce-throttle.swift
//
// Created by Simon Ljungberg on 19/12/16.
// License: MIT
//
import Foundation
extension TimeInterval {
/**
Checks if `since` has passed since `self`.
- Parameter since: The duration of time that needs to have passed for this function to return `true`.
- Returns: `true` if `since` has passed since now.
*/
func hasPassed(since: TimeInterval) -> Bool {
return Date().timeIntervalSinceReferenceDate - self > since
}
}
/**
Wraps a function in a new function that will only execute the wrapped function if `delay` has passed without this function being called.
- Parameter delay: A `DispatchTimeInterval` to wait before executing the wrapped function after last invocation.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to debounce. Can't accept any arguments.
- Returns: A new function that will only call `action` if `delay` time passes between invocations.
*/
func debounce(delay: DispatchTimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void)) -> () -> Void {
var currentWorkItem: DispatchWorkItem?
return {
currentWorkItem?.cancel()
currentWorkItem = DispatchWorkItem { action() }
queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
/**
Wraps a function in a new function that will only execute the wrapped function if `delay` has passed without this function being called.
Accepsts an `action` with one argument.
- Parameter delay: A `DispatchTimeInterval` to wait before executing the wrapped function after last invocation.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to debounce. Can accept one argument.
- Returns: A new function that will only call `action` if `delay` time passes between invocations.
*/
func debounce1<T>(delay: DispatchTimeInterval, queue: DispatchQueue = .main, action: @escaping ((T) -> Void)) -> (T) -> Void {
var currentWorkItem: DispatchWorkItem?
return { (p1: T) in
currentWorkItem?.cancel()
currentWorkItem = DispatchWorkItem { action(p1) }
queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
/**
Wraps a function in a new function that will only execute the wrapped function if `delay` has passed without this function being called.
Accepsts an `action` with two arguments.
- Parameter delay: A `DispatchTimeInterval` to wait before executing the wrapped function after last invocation.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to debounce. Can accept two arguments.
- Returns: A new function that will only call `action` if `delay` time passes between invocations.
*/
func debounce2<T, U>(delay: DispatchTimeInterval, queue: DispatchQueue = .main, action: @escaping ((T, U) -> Void)) -> (T, U) -> Void {
var currentWorkItem: DispatchWorkItem?
return { (p1: T, p2: U) in
currentWorkItem?.cancel()
currentWorkItem = DispatchWorkItem { action(p1, p2) }
queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
/**
Wraps a function in a new function that will throttle the execution to once in every `delay` seconds.
- Parameter delay: A `TimeInterval` specifying the number of seconds that needst to pass between each execution of `action`.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to throttle.
- Returns: A new function that will only call `action` once every `delay` seconds, regardless of how often it is called.
*/
func throttle(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void)) -> () -> Void {
var currentWorkItem: DispatchWorkItem?
var lastFire: TimeInterval = 0
return {
guard currentWorkItem == nil else { return }
currentWorkItem = DispatchWorkItem {
action()
lastFire = Date().timeIntervalSinceReferenceDate
currentWorkItem = nil
}
delay.hasPassed(since: lastFire) ? queue.async(execute: currentWorkItem!) : queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
/**
Wraps a function in a new function that will throttle the execution to once in every `delay` seconds.
Accepts an `action` with one argument.
- Parameter delay: A `TimeInterval` specifying the number of seconds that needst to pass between each execution of `action`.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to throttle. Can accept one argument.
- Returns: A new function that will only call `action` once every `delay` seconds, regardless of how often it is called.
*/
func throttle1<T>(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping ((T) -> Void)) -> (T) -> Void {
var currentWorkItem: DispatchWorkItem?
var lastFire: TimeInterval = 0
return { (p1: T) in
guard currentWorkItem == nil else { return }
currentWorkItem = DispatchWorkItem {
action(p1)
lastFire = Date().timeIntervalSinceReferenceDate
currentWorkItem = nil
}
delay.hasPassed(since: lastFire) ? queue.async(execute: currentWorkItem!) : queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
/**
Wraps a function in a new function that will throttle the execution to once in every `delay` seconds.
Accepts an `action` with two arguments.
- Parameter delay: A `TimeInterval` specifying the number of seconds that needst to pass between each execution of `action`.
- Parameter queue: The queue to perform the action on. Defaults to the main queue.
- Parameter action: A function to throttle. Can accept two arguments.
- Returns: A new function that will only call `action` once every `delay` seconds, regardless of how often it is called.
*/
func throttle2<T, U>(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping ((T, U) -> Void)) -> (T, U) -> Void {
var currentWorkItem: DispatchWorkItem?
var lastFire: TimeInterval = 0
return { (p1: T, p2: U) in
guard currentWorkItem == nil else { return }
currentWorkItem = DispatchWorkItem {
action(p1, p2)
lastFire = Date().timeIntervalSinceReferenceDate
currentWorkItem = nil
}
delay.hasPassed(since: lastFire) ? queue.async(execute: currentWorkItem!) : queue.asyncAfter(deadline: .now() + delay, execute: currentWorkItem!)
}
}
@simme

This comment has been minimized.

Show comment
Hide comment
@simme

simme Dec 20, 2016

Usage example:

func bar(bar: String) {
    print("Bar", bar)
}

func testThrottle() {
    let queue = DispatchQueue.global(qos: .background)
    let fn = throttle1(delay: 0.4, queue: queue, action: bar)
    for i in 0...9 {
        fn("\(i)")
        Thread.sleep(forTimeInterval: 0.2)
    }
}

testThrottle()
Owner

simme commented Dec 20, 2016

Usage example:

func bar(bar: String) {
    print("Bar", bar)
}

func testThrottle() {
    let queue = DispatchQueue.global(qos: .background)
    let fn = throttle1(delay: 0.4, queue: queue, action: bar)
    for i in 0...9 {
        fn("\(i)")
        Thread.sleep(forTimeInterval: 0.2)
    }
}

testThrottle()
@Dane456

This comment has been minimized.

Show comment
Hide comment
@Dane456

Dane456 May 17, 2018

Thank you!!

Dane456 commented May 17, 2018

Thank you!!

@Dane456

This comment has been minimized.

Show comment
Hide comment
@Dane456

Dane456 Jun 3, 2018

If you want throttle to trigger instantaneously on the first call instead of waiting delay seconds:

func throttle(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void)) -> () -> Void {
        var lastFireTime = 0.0
        var currentWorkItem:DispatchWorkItem?
        return {
            if delay.hasPassed(since: lastFireTime) {
                currentWorkItem = DispatchWorkItem {
                    action()
                    lastFireTime = Date.timeIntervalSinceReferenceDate
                }
                queue.async(execute: currentWorkItem!)
            }
        }
    }

Dane456 commented Jun 3, 2018

If you want throttle to trigger instantaneously on the first call instead of waiting delay seconds:

func throttle(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void)) -> () -> Void {
        var lastFireTime = 0.0
        var currentWorkItem:DispatchWorkItem?
        return {
            if delay.hasPassed(since: lastFireTime) {
                currentWorkItem = DispatchWorkItem {
                    action()
                    lastFireTime = Date.timeIntervalSinceReferenceDate
                }
                queue.async(execute: currentWorkItem!)
            }
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment