throttler 实现在限定时
22 August 2023
Throttler 实现在限定时间内执行限定的task
class LiveThrottler {
private let serialQueue = DispatchQueue.main // DispatchQueue(label: "com.live.throttler.serial.queue")
private var workItems = [(priority: Int, workItem: DispatchWorkItem)]()
private var currentPriority: Int = 0
private var lastExecutionTime: DispatchTime = .now()
private let maxTaskCount: Int
init(maxTaskCount: Int = 1) {
self.maxTaskCount = maxTaskCount
}
func throttle(timeInterval: TimeInterval = 1.0, priority: Int = 0, block: @escaping () -> Void) {
let workItem = DispatchWorkItem { block() }
serialQueue.async { [weak self] in
guard let self = self else { return }
self.workItems.append((priority, workItem))
// remove old items
if self.workItems.count > self.maxTaskCount {
//self.workItems.removeFirst(self.workItems.count - self.maxTaskCount)
self.removeLowestPriorityItem()
}
// schedule and execute
let now = DispatchTime.now()
let delta = now.uptimeNanoseconds - self.lastExecutionTime.uptimeNanoseconds
let deltaInSeconds = Double(delta) / 1_000_000_000
if deltaInSeconds >= timeInterval {
self.execute(workItem)
} else {
let delay = timeInterval - deltaInSeconds
self.schedule(workItem, after: delay)
}
}
}
private func schedule(_ workItem: DispatchWorkItem, after delay: TimeInterval) {
let dispatchDelay = DispatchTimeInterval.milliseconds(Int(delay * 1000))
serialQueue.asyncAfter(deadline: .now() + dispatchDelay, execute: workItem)
}
private func execute(_ workItem: DispatchWorkItem) {
workItem.perform()
serialQueue.async { [weak self] in
guard let self = self else { return }
if let index = self.workItems.firstIndex(where: { $0.workItem === workItem }) {
self.workItems.remove(at: index)
}
}
self.lastExecutionTime = DispatchTime.now()
}
private func removeLowestPriorityItem() {
guard let lowestPriorityItem = workItems.min(by: { $0.priority < $1.priority }) else { return }
serialQueue.async { [weak self] in
guard let self = self else { return }
if let index = self.workItems.firstIndex(where: { $0.priority == lowestPriorityItem.priority }) {
let removedItem = self.workItems.remove(at: index)
removedItem.workItem.cancel()
} else {
let overflow = self.workItems.count - self.maxTaskCount
if overflow > 0 {
let removedItems = self.workItems.prefix(overflow)
self.workItems.removeFirst(overflow)
for item in removedItems {
item.workItem.cancel()
}
}
}
}
}
//MARK: repeat call
static func repeatCall(n: Int = 1, during seconds: TimeInterval = 1.0, delay: TimeInterval = 0, task: @escaping (_ index: Int) -> Void) {
let queue = DispatchQueue(label: "com.live.throttler.concurrent.queue", attributes: .concurrent)
//let queue = DispatchQueue.main
queue.asyncAfter(deadline: .now() + delay) {
let dispatchGroup = DispatchGroup()
var taskCount = 0
var isCancelled = false
for i in 0..<n {
dispatchGroup.enter()
let deadline = DispatchTime.now() + (Double(i) * seconds) / Double(n)
queue.asyncAfter(deadline: deadline) {
guard !isCancelled else {
dispatchGroup.leave()
return
}
DispatchQueue.main.async {
task(i)
}
taskCount += 1
if taskCount == n {
dispatchGroup.leave()
}
}
}
queue.asyncAfter(deadline: .now() + seconds) {
if taskCount < n {
isCancelled = true
debugPrint("Timeout: Not all tasks completed within \(seconds) seconds.")
dispatchGroup.leave()
} else {
debugPrint("All tasks completed within \(seconds) seconds.")
}
}
dispatchGroup.wait()
}
}
}
//MARK: WidgetExposureThrottler
class WidgetExposureThrottler {
private var workItem: DispatchWorkItem?
func throttle(timeInterval: TimeInterval = 1.0, _ block: @escaping () -> Void) {
workItem?.cancel()
let newWorkItem = DispatchWorkItem { block() }
workItem = newWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval, execute: newWorkItem)
}
}
