Icon credits: Lorc, Delapouite & contributors
One Line to throttle, debounce and delay: Say Goodbye to Reactive Programming such as RxSwift and Combine.
import Throttler
/// throttle
for i in (0...10000000) {
throttle {
print(i)
}
}
// 0
// 3210779
// 6509981
// 9809756
// specify an interval
(0...100000).forEach { i in
throttle(.seconds(0.01)) {
print(i)
}
}
// 0
// 18133
// 36058
// 57501
// 82851
/// debounce
debounce {
print("debounce with 1 second interval")
}
debounce(.seconds(3)) {
print("debounce with 3 seconds interval")
}
/// delay
delay {
print("fired after 1 sec")
}
delay(.seconds(2)) {
print("fired after 2 sec")
}import SwiftUI
import Throttler
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Button(action: {
if #available(iOS 16.0, *) {
for i in (0...10000000) {
throttle {
print(i)
}
}
} else {
for i in (0...10000000) {
throttle(seconds: 0.01) {
print(i)
}
}
}
// 0
// 3210779
// 6509981
// 9809756
}) {
Text("throttle")
}
Button(action: {
if #available(iOS 16.0, *) {
delay(.seconds(2)) {
print("fired after 2 sec")
}
// delay {
// print("fired after 1 sec")
// }
} else {
delay(seconds: 2) {
print("fired after 2 sec")
}
}
// (delay 2 second..)
// ...
// fired after 2 sec
}) {
Text("delay")
}
Button(action: {
if #available(iOS 16.0, *) {
debounce {
print("fired after 1 second")
}
} else {
debounce(seconds: 1.0, on: .main) {
print("fired after 1 second")
}
}
// (click a button as fast as you can)
// ....
// ....
// ....
// fired after 1 second
}) {
Text("""
debounce
(click a button continuously as fast as you can)
""")
}
}
}
}- Throttler
import Throttler
var sum = 0
for i in 0...10 {
print("for loop : \(i)")
// equivalent to throttle RxSwift and Combine provides by default.
Throttler.throttle(delay: .milliseconds(10)) {
sum += 1
print("sum : \(sum)")
}
}
// for loop : 0
// sum : 1
// for loop : 1
// for loop : 2
// sum : 2
// for loop : 3
// for loop : 4
// for loop : 5
// for loop : 6
// sum : 3
// for loop : 7
// for loop : 8
// for loop : 9
// for loop : 10
// sum : 4- Debouncer
import Throttler
// advanced debounce, running a first task immediately before initiating debounce.
for i in 1...1000 {
Debouncer.debounce {
print("debounce! > \(i)")
}
}
// debounce! > 1
// debounce! > 1000
// equivalent to debounce of Combine, RxSwift.
for i in 1...1000 {
Debouncer.debounce(shouldRunImmediately: false) {
print("debounce! > \(i)")
}
}
// debounce! > 1000Throttler can do advanced debounce feature, running a first event immediately before initiating debounce that Combine and RxSwift don't have by default.
You could, but you may need a complex implementation yourself for that.
For example, Throttler can abstract away this kind of implementation https://stackoverflow.com/a/60307697/3426053
into
import Throttler
for i in 1...1000 {
Debouncer.debounce {
print("debounce! > \(i)")
}
}
// debounce! > 1
// debounce! > 1000That's it
While it is originally developed to solve the problem where vast number of user typing input
involving CPU intensive tasks have be to performed repeatedly and constantly
on HLVM,
A common problem that Throttler can solve is
a user taps a button that requests asynchronous network call a massive number of times
within few seconds.
With Throttler,
import UIKit
import Throttler
class ViewController: UIViewController {
@IBOutlet var button: UIButton!
var index = 0
/********
Assuming your users will tap the button, and
request asyncronous network call 10 times(maybe more?) in a row within very short time nonstop.
*********/
@IBAction func click(_ sender: Any) {
print("click1!")
Debouncer.debounce {
// Imaging this is a time-consuming and resource-heavy task that takes an unknown amount of time!
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
self.index += 1
print("click1 : \(self.index) : \(String(data: data, encoding: .utf8)!)")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}Output:
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}Without Throttler
class ViewController: UIViewController {
@IBOutlet var button: UIButton!
var index = 0
/********
Assuming your users will tap the button, and
request asyncronous network call 10 times(maybe more?) in a row within very short time nonstop.
*********/
@IBAction func click(_ sender: Any) {
print("click1!")
// Imaging this is a time-consuming and resource-heavy task that takes an unknown amount of time!
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
self.index += 1
print("click1 : \(self.index) : \(String(data: data, encoding: .utf8)!)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}if you don't use Throttler, Output is as follows:
/*
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
.......
......
.....
...
..
.
Your server will be hell busy trying to response all the time (putting cache aside)
πππ
*/- One liner, no brainer. you can just drop the one line code and it will get up and running out of box.
- For those who don't prefer Reactive programming to do debounce and throttle operation, you don't have to go to reactive programming like black magic in some sense.
iOS 13.0, macOS 10.15 (To use latest version API, iOS 16.0 and macOS 13.0 are required.)
if #available(iOS 16.0, *) {
for i in (0...10000000) {
throttle {
print(i)
}
}
} else {
for i in (0...10000000) {
throttle(seconds: 0.01) {
print(i)
}
}
}
@available(macOS 13.0, *)
@available(iOS 16.0, *)
public func throttle(
_ interval: Duration = .seconds(1),
on actorType: ActorType = .main,
operation: @escaping () -> Void
) {
let now = Date()
if let lastExecution = lastExecutionDate, now.timeIntervalSince(lastExecution) < interval.timeInterval { return }
lastExecutionDate = now
Task {
actorType ~= .main ? await MainActor.run { operation() } : operation()
}
}- File > Swift Packages > Add Package Dependency
- Add
https://github.com/boraseoksoon/Throttler.git - Click Next.
- Done.
- Provide an option that ensures the final execution of a job, regardless of whether it has been throttled or debounced.
- Write more useful functions that can accomplish tasks in a single line of code.
Pull requests are warmly welcome as well.
Throttler is released under the MIT license. See LICENSE for details.