Files
NetNewsWire/Core/Sources/Core/CoalescingQueue.swift
2024-04-07 17:06:39 -07:00

100 lines
2.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// CoalescingQueue.swift
// RSCore
//
// Created by Brent Simmons on 2/17/18.
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
//
import Foundation
// Use when you want to coalesce calls for something like updating visible table cells.
// Calls are uniqued. If you add a call with the same target and selector as a previous call, youll just get one call.
// Targets are weakly-held. If a target goes to nil, the call is not performed.
// The perform date is pushed off every time a call is added.
// Calls are FIFO.
struct QueueCall: Equatable {
weak var target: AnyObject?
let selector: Selector
func perform() {
let _ = target?.perform(selector)
}
static func ==(lhs: QueueCall, rhs: QueueCall) -> Bool {
return lhs.target === rhs.target && lhs.selector == rhs.selector
}
}
@MainActor @objc public final class CoalescingQueue: NSObject {
@MainActor public static let standard = CoalescingQueue(name: "Standard", interval: 0.05, maxInterval: 0.1)
public let name: String
public var isPaused = false
private let interval: TimeInterval
private let maxInterval: TimeInterval
private var lastCallTime = Date.distantFuture
private var timer: Timer? = nil
private var calls = [QueueCall]()
public init(name: String, interval: TimeInterval = 0.05, maxInterval: TimeInterval = 2.0) {
self.name = name
self.interval = interval
self.maxInterval = maxInterval
}
public func add(_ target: AnyObject, _ selector: Selector) {
let queueCall = QueueCall(target: target, selector: selector)
add(queueCall)
if Date().timeIntervalSince1970 - lastCallTime.timeIntervalSince1970 > maxInterval {
timerDidFire(nil)
}
}
public func performCallsImmediately() {
guard !isPaused else { return }
let callsToMake = calls // Make a copy in case calls are added to the queue while performing calls.
resetCalls()
for call in callsToMake {
call.perform()
}
}
@objc func timerDidFire(_ sender: Any?) {
lastCallTime = Date()
performCallsImmediately()
}
}
private extension CoalescingQueue {
func add(_ call: QueueCall) {
restartTimer()
if !calls.contains(call) {
calls.append(call)
}
}
func resetCalls() {
calls = [QueueCall]()
}
func restartTimer() {
invalidateTimer()
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(timerDidFire(_:)), userInfo: nil, repeats: false)
}
func invalidateTimer() {
if let timer = timer, timer.isValid {
timer.invalidate()
}
timer = nil
}
}