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)
""")
}
}
}
}
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
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! > 1000
Throttler 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! > 1000
That'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)
😂😂😂
*/
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()
}
}
https://github.com/boraseoksoon/Throttler.git
Pull requests are warmly welcome as well.
Throttler is released under the MIT license. See LICENSE for details.
link |
Stars: 78 |
Last commit: 1 week ago |
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics