Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active June 24, 2020 11:13
Show Gist options
  • Select an option

  • Save KaQuMiQ/b2fbabb2fa208b9d83e1a8d99bf70b74 to your computer and use it in GitHub Desktop.

Select an option

Save KaQuMiQ/b2fbabb2fa208b9d83e1a8d99bf70b74 to your computer and use it in GitHub Desktop.
import struct Dispatch.DispatchTime
import protocol Dispatch.DispatchSourceTimer
import class Dispatch.DispatchSource
import class Dispatch.DispatchQueue
import struct Foundation.NSDate.TimeInterval
import class Foundation.NSLock.NSRecursiveLock
public final class Throttle {
public let queue: DispatchQueue
private var task: (() -> Void)?
private var timer: DispatchSourceTimer? {
didSet { oldValue?.cancel() }
}
private let intervalMicroseconds: UInt64
private let rescheduleOnPush: Bool
private let lock: NSRecursiveLock = NSRecursiveLock()
public init(interval: TimeInterval, rescheduleOnPush: Bool = false, queue: DispatchQueue) {
self.queue = queue
self.rescheduleOnPush = rescheduleOnPush
self.intervalMicroseconds = UInt64(interval * 1000000000)
}
deinit {
lock.lock()
timer?.cancel()
}
public func push(_ task: @escaping () -> Void, on queue: DispatchQueue? = nil) {
lock.lock()
defer { lock.unlock() }
self.task = task
guard self.timer == nil || rescheduleOnPush else { return }
self.timer = scheduleTimer()
}
public func cancel() {
lock.lock()
defer { lock.unlock() }
self.timer = nil
self.task = nil
}
private func execute() {
lock.lock()
defer { lock.unlock() }
assert(timer != nil)
defer {
timer?.cancel()
timer = nil
}
guard let task = self.task else { return assertionFailure("Missing task on throttle execution.") }
defer { self.task = nil }
task()
}
private func scheduleTimer() -> DispatchSourceTimer {
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + intervalMicroseconds))
timer.setEventHandler { [weak self] in
guard let self = self else { return }
self.execute()
}
timer.activate()
return timer
}
}
@KaQuMiQ
Copy link
Author

KaQuMiQ commented Jun 24, 2020

Example:

let throttle = Throttle(interval: 1.5 queue: DispatchQueue(label: "throttle.queue"))
throttle.push { /* task */ }

Task will be executed on specified DispatchQueue while being delayed by interval. On subsequent push method calls the task will be replaced until specified time interval passes counting from first push when Throttle was idle. Pushing new tasks after execution will cause next timer start.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment