diff --git a/Core/Sources/Core/DataFile.swift b/Core/Sources/Core/DataFile.swift index 144cbb2b4..c2e34a467 100644 --- a/Core/Sources/Core/DataFile.swift +++ b/Core/Sources/Core/DataFile.swift @@ -21,25 +21,18 @@ public protocol DataFileDelegate: AnyObject { private var isDirty = false { didSet { if isDirty { - postponingBlock.runInFuture() - } - else { - postponingBlock.cancelRun() + saveQueue.add(self, #selector(saveToDiskIfNeeded)) } } } private let fileURL: URL - - private lazy var postponingBlock: PostponingBlock = { - PostponingBlock(name: "DataFile \(fileURL.absoluteString)", delayInterval: 1.0) { [weak self] in - self?.saveToDiskIfNeeded() - } - }() + private let saveQueue: CoalescingQueue public init(fileURL: URL) { self.fileURL = fileURL + self.saveQueue = CoalescingQueue(name: "DataFile \(fileURL.absoluteString)", interval: 1.0, maxInterval: 2.0) } public func markAsDirty() { @@ -66,7 +59,7 @@ public protocol DataFileDelegate: AnyObject { private extension DataFile { - func saveToDiskIfNeeded() { + @objc func saveToDiskIfNeeded() { if isDirty { save() diff --git a/Core/Sources/Core/PostponingBlock.swift b/Core/Sources/Core/PostponingBlock.swift deleted file mode 100644 index 04119a048..000000000 --- a/Core/Sources/Core/PostponingBlock.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// PostponingBlock.swift -// -// -// Created by Brent Simmons on 6/9/24. -// - -import Foundation -import os - -/// Runs a block of code in the future. Each time `runInFuture` is called, the block is postponed again until the future by `delayInterval`. -@MainActor public final class PostponingBlock { - - public static let delayIntervalForUI: TimeInterval = 0.05 - - private let block: @MainActor () -> Void - private let delayInterval: TimeInterval - private let name: String // For debugging - private var timer: Timer? - - private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PostponingBlock") - - public init(name: String, delayInterval: TimeInterval = PostponingBlock.delayIntervalForUI, block: @MainActor @escaping () -> Void) { - - self.delayInterval = delayInterval - self.name = name - self.block = block - } - - /// Run the block in `delayInterval` seconds, canceling any run about to happen before then. - public func runInFuture() { - - invalidateTimer() - - timer = Timer.scheduledTimer(withTimeInterval: delayInterval, repeats: false) { [weak self] timer in - MainActor.assumeIsolated { - self?.block() - } - } - } - - /// Cancel any upcoming run. - public func cancelRun() { - - invalidateTimer() - } -} - -private extension PostponingBlock { - - func invalidateTimer() { - - if let timer, timer.isValid { - timer.invalidate() - logger.info("Canceling existing timer in PostponingBlock: \(self.name)") - } - timer = nil - } -} diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 3bac0fd43..5bd3882d3 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -70,10 +70,6 @@ import Sparkle @IBOutlet var groupArticlesByFeedMenuItem: NSMenuItem! @IBOutlet var checkForUpdatesMenuItem: NSMenuItem! - private lazy var postponingUpdateDockBadgeBlock: PostponingBlock = { - PostponingBlock(name: "Update Dock Badge", delayInterval: 0.05, block: updateDockBadge) - }() - var unreadCount = 0 { didSet { if unreadCount != oldValue { @@ -542,10 +538,11 @@ import Sparkle // MARK: - Dock Badge func queueUpdateDockBadge() { - postponingUpdateDockBadgeBlock.runInFuture() + + CoalescingQueue.standard.add(self, #selector(updateDockBadge)) } - func updateDockBadge() { + @objc func updateDockBadge() { let label = unreadCount > 0 ? "\(unreadCount)" : "" NSApplication.shared.dockTile.badgeLabel = label } diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index d9c7f48e0..4fc9e9130 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -59,12 +59,6 @@ final class MainWindowController : NSWindowController, NSUserInterfaceValidation } private var searchSmartFeed: SmartFeed? = nil private var restoreArticleWindowScrollY: CGFloat? - - private lazy var postponingMakeToolbarValidateBlock: PostponingBlock = { - PostponingBlock(name: "Make Toolbar Validate") { [weak self] in - self?.makeToolbarValidate() - } - }() // MARK: - NSWindowController @@ -203,11 +197,11 @@ final class MainWindowController : NSWindowController, NSUserInterfaceValidation func queueMakeToolbarValidate() { - postponingMakeToolbarValidateBlock.runInFuture() + CoalescingQueue.standard.add(self, #selector(makeToolbarValidate)) } - func makeToolbarValidate() { - + @objc func makeToolbarValidate() { + window?.toolbar?.validateVisibleItems() } diff --git a/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift b/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift index bab58ae9e..0d8793c03 100644 --- a/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift +++ b/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift @@ -23,9 +23,10 @@ final class SidebarStatusBarView: NSView { private var progress: CombinedRefreshProgress? = nil { didSet { - CoalescingQueue.standard.add(self, #selector(updateUI)) + queueUpdateUI() } } + override var isFlipped: Bool { return true } @@ -42,6 +43,11 @@ final class SidebarStatusBarView: NSView { NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) } + func queueUpdateUI() { + + CoalescingQueue.standard.add(self, #selector(updateUI)) + } + @objc func updateUI() { guard let progress = progress else { diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index 509efecb6..53395d3e1 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -48,14 +48,8 @@ import Images } #endif - private lazy var postponingBlock: PostponingBlock = { - PostponingBlock(name: "SmartFeed", delayInterval: 1.0) { - Task { - try? await self.fetchUnreadCounts() - } - } - }() - + private let fetchUnreadCountsQueue = CoalescingQueue(name: "SmartFeed", interval: 1.0, maxInterval: 2.0) + private var fetchUnreadCountsTask: Task? private let delegate: SmartFeedDelegate private var unreadCounts = [String: Int]() @@ -71,25 +65,28 @@ import Images queueFetchUnreadCounts() } - func fetchUnreadCounts() async throws { - - let activeAccounts = AccountManager.shared.activeAccounts - - // Remove any accounts that are no longer active or have been deleted - let activeAccountIDs = activeAccounts.map { $0.accountID } - for accountID in unreadCounts.keys { - if !activeAccountIDs.contains(accountID) { - unreadCounts.removeValue(forKey: accountID) + @objc func fetchUnreadCounts() { + + Task { + + let activeAccounts = AccountManager.shared.activeAccounts + + // Remove any accounts that are no longer active or have been deleted + let activeAccountIDs = activeAccounts.map { $0.accountID } + for accountID in unreadCounts.keys { + if !activeAccountIDs.contains(accountID) { + unreadCounts.removeValue(forKey: accountID) + } + } + + if activeAccounts.isEmpty { + updateUnreadCount() + return + } + + for account in activeAccounts { + await fetchUnreadCount(for: account) } - } - - if activeAccounts.isEmpty { - updateUnreadCount() - return - } - - for account in activeAccounts { - await fetchUnreadCount(for: account) } } } @@ -111,7 +108,7 @@ private extension SmartFeed { func queueFetchUnreadCounts() { - postponingBlock.runInFuture() + fetchUnreadCountsQueue.add(self, #selector(fetchUnreadCounts)) } func fetchUnreadCount(for account: Account) async {