Last active
September 20, 2022 07:06
-
-
Save VladimirBrejcha/a3f0c305aa5db3b97919eaa9c108770d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class Throttle { | |
private let queue: DispatchQueue | |
private let delay: Double | |
private var delayedBlock: (() -> Void)? | |
private var cancelBlock: (() -> Void)? | |
private var timer: DispatchSourceTimer? | |
private var isReady = true | |
private var hasDelayedBlock: Bool { delayedBlock != nil } | |
init(queue: DispatchQueue, delay: Double) { | |
self.queue = queue | |
self.delay = delay | |
} | |
func callAsFunction(action: @escaping () -> Void, onCancel: @escaping () -> Void) { | |
queue.async { [weak self] in | |
guard let self = self else { | |
onCancel() | |
return | |
} | |
if self.isReady { | |
self.isReady = false | |
action() | |
self.scheduleTimer() | |
} else { | |
self.cancelBlock?() | |
self.cancelBlock = onCancel | |
self.delayedBlock = action | |
} | |
} | |
} | |
private func scheduleTimer() { | |
let timer = DispatchSource.makeTimerSource(queue: queue) | |
timer.setEventHandler { [weak self] in | |
guard let self = self else { return } | |
if self.hasDelayedBlock { | |
self.cancelBlock = nil | |
self.delayedBlock?() | |
self.delayedBlock = nil | |
self.scheduleTimer() | |
} else { | |
self.isReady = true | |
} | |
} | |
timer.schedule(deadline: .now() + delay) | |
timer.resume() | |
self.timer = timer | |
} | |
} | |
// MARK: - Tests - | |
func print(_ msg: String) { | |
let formatter = DateFormatter() | |
formatter.dateFormat = "ss.SSSS" | |
Swift.print("\(formatter.string(from: Date())) \(msg)") | |
} | |
let queue = DispatchQueue(label: "ThrottleQueue") | |
let throttle: Throttle = Throttle(queue: queue, delay: 0.5) | |
throttle { | |
print("action 1 done") | |
} onCancel: { | |
print("action 1 cancelled") | |
} | |
DispatchQueue.global().async { | |
throttle { | |
print("action 2 done") | |
} onCancel: { | |
print("action 2 cancelled") | |
} | |
} | |
queue.asyncAfter(deadline: DispatchTime.now() + 0.2) { | |
throttle { | |
print("action 3 done") | |
} onCancel: { | |
print("action 3 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.3) { | |
throttle { | |
print("action 4 done") | |
} onCancel: { | |
print("action 4 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.4) { | |
throttle { | |
print("action 5 done") | |
} onCancel: { | |
print("action 5 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.5) { | |
throttle { | |
print("action 6 done") | |
} onCancel: { | |
print("action 6 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.7) { | |
throttle { | |
print("action 7 done") | |
} onCancel: { | |
print("action 7 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.75) { | |
throttle { | |
print("action 8 done") | |
} onCancel: { | |
print("action 8 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.8) { | |
throttle { | |
print("action 9 done") | |
} onCancel: { | |
print("action 9 cancelled") | |
} | |
} | |
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 1.3) { | |
throttle { | |
print("action 10 done") | |
} onCancel: { | |
print("action 10 cancelled") | |
} | |
} | |
queue.asyncAfter(deadline: DispatchTime.now() + 1.5) { | |
throttle { | |
print("action 11 done") | |
} onCancel: { | |
print("action 11 cancelled") | |
} | |
} | |
queue.asyncAfter(deadline: DispatchTime.now() + 2.5) { | |
throttle { | |
print("action 12 done") | |
} onCancel: { | |
print("action 12 cancelled") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment