请稍侯

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)
    }
}