diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 231ed896a..823de932d 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -723,7 +723,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, try await database.unreadArticles(feedIDs: Set([feed.feedID])) } - public func unreadArticles(feeds: Set) async throws -> Set
{ + @MainActor public func unreadArticles(feeds: Set) async throws -> Set
{ if feeds.isEmpty { return Set
() @@ -1011,19 +1011,19 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, NotificationCenter.default.post(name: .AccountRefreshProgressDidChange, object: self) } - @objc func unreadCountDidChange(_ note: Notification) { + @MainActor @objc func unreadCountDidChange(_ note: Notification) { if let feed = note.object as? Feed, feed.account === self { updateUnreadCount() } } - @objc func batchUpdateDidPerform(_ note: Notification) { + @MainActor @objc func batchUpdateDidPerform(_ note: Notification) { flattenedFeedsNeedUpdate = true rebuildFeedDictionaries() updateUnreadCount() } - @objc func childrenDidChange(_ note: Notification) { + @MainActor @objc func childrenDidChange(_ note: Notification) { guard let object = note.object else { return } @@ -1165,7 +1165,7 @@ private extension Account { return database.fetchArticlesAsync(articleIDs: articleIDs, completion) } - func articles(container: Container) async throws -> Set
{ + @MainActor func articles(container: Container) async throws -> Set
{ let feeds = container.flattenedFeeds() let articles = try await database.articles(feedIDs: allFeedIDs()) @@ -1178,17 +1178,20 @@ private extension Account { func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) { let feeds = container.flattenedFeeds() database.fetchArticlesAsync(feeds.feedIDs()) { [weak self] (articleSetResult) in - switch articleSetResult { - case .success(let articles): - self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) - completion(.success(articles)) - case .failure(let databaseError): - completion(.failure(databaseError)) + + Task { @MainActor [weak self] in + switch articleSetResult { + case .success(let articles): + self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) + completion(.success(articles)) + case .failure(let databaseError): + completion(.failure(databaseError)) + } } } } - func unreadArticles(container: Container, limit: Int? = nil) async throws -> Set
{ + @MainActor func unreadArticles(container: Container, limit: Int? = nil) async throws -> Set
{ let feeds = container.flattenedFeeds() let feedIDs = feeds.feedIDs() @@ -1206,23 +1209,26 @@ private extension Account { func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) { let feeds = container.flattenedFeeds() database.fetchUnreadArticlesAsync(feeds.feedIDs(), limit) { [weak self] (articleSetResult) in - switch articleSetResult { - case .success(let articles): - - // We don't validate limit queries because they, by definition, won't correctly match the - // complete unread state for the given container. - if limit == nil { - self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) + + Task { @MainActor [weak self] in + switch articleSetResult { + case .success(let articles): + + // We don't validate limit queries because they, by definition, won't correctly match the + // complete unread state for the given container. + if limit == nil { + self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) + } + + completion(.success(articles)) + case .failure(let databaseError): + completion(.failure(databaseError)) } - - completion(.success(articles)) - case .failure(let databaseError): - completion(.failure(databaseError)) } } } - func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set, _ articles: Set
) { + @MainActor func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set, _ articles: Set
) { // Validate unread counts. This was the site of a performance slowdown: // it was calling going through the entire list of articles once per feed: // feeds.forEach { validateUnreadCount($0, articles) } @@ -1302,7 +1308,7 @@ private extension Account { feedDictionariesNeedUpdate = false } - func updateUnreadCount() { + @MainActor func updateUnreadCount() { if fetchingAllUnreadCounts { return } @@ -1356,44 +1362,52 @@ private extension Account { func fetchUnreadCount(_ feed: Feed, _ completion: VoidCompletionBlock?) { database.fetchUnreadCount(feed.feedID) { result in - if let unreadCount = try? result.get() { - feed.unreadCount = unreadCount + Task { @MainActor in + if let unreadCount = try? result.get() { + feed.unreadCount = unreadCount + } + completion?() } - completion?() } } func fetchUnreadCounts(_ feeds: Set, _ completion: VoidCompletionBlock?) { let feedIDs = Set(feeds.map { $0.feedID }) database.fetchUnreadCounts(for: feedIDs) { result in - if let unreadCountDictionary = try? result.get() { - self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds) + + Task { @MainActor in + if let unreadCountDictionary = try? result.get() { + self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds) + } + completion?() } - completion?() } } func fetchAllUnreadCounts(_ completion: VoidCompletionBlock? = nil) { fetchingAllUnreadCounts = true database.fetchAllUnreadCounts { result in - guard let unreadCountDictionary = try? result.get() else { + + Task { @MainActor in + guard let unreadCountDictionary = try? result.get() else { + completion?() + return + } + self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds()) + + self.fetchingAllUnreadCounts = false + self.updateUnreadCount() + + if !self.isUnreadCountsInitialized { + self.isUnreadCountsInitialized = true + self.postUnreadCountDidInitializeNotification() + } completion?() - return } - self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds()) - - self.fetchingAllUnreadCounts = false - self.updateUnreadCount() - - if !self.isUnreadCountsInitialized { - self.isUnreadCountsInitialized = true - self.postUnreadCountDidInitializeNotification() - } - completion?() } } - func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set) { + @MainActor func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set) { for feed in feeds { // When the unread count is zero, it won’t appear in unreadCountDictionary. let unreadCount = unreadCountDictionary[feed.feedID] ?? 0 diff --git a/Account/Sources/Account/AccountManager.swift b/Account/Sources/Account/AccountManager.swift index fa1e19bc7..18736c97e 100644 --- a/Account/Sources/Account/AccountManager.swift +++ b/Account/Sources/Account/AccountManager.swift @@ -148,7 +148,7 @@ public final class AccountManager: UnreadCountProvider { return account } - public func deleteAccount(_ account: Account) { + @MainActor public func deleteAccount(_ account: Account) { guard !account.refreshInProgress else { return } @@ -409,7 +409,7 @@ public final class AccountManager: UnreadCountProvider { // MARK: - Notifications - @objc func unreadCountDidInitialize(_ notification: Notification) { + @MainActor @objc func unreadCountDidInitialize(_ notification: Notification) { guard let _ = notification.object as? Account else { return } @@ -418,14 +418,14 @@ public final class AccountManager: UnreadCountProvider { } } - @objc dynamic func unreadCountDidChange(_ notification: Notification) { + @MainActor @objc dynamic func unreadCountDidChange(_ notification: Notification) { guard let _ = notification.object as? Account else { return } updateUnreadCount() } - @objc func accountStateDidChange(_ notification: Notification) { + @MainActor @objc func accountStateDidChange(_ notification: Notification) { updateUnreadCount() } } @@ -434,7 +434,7 @@ public final class AccountManager: UnreadCountProvider { private extension AccountManager { - func updateUnreadCount() { + @MainActor func updateUnreadCount() { unreadCount = calculateUnreadCount(activeAccounts) } diff --git a/Account/Sources/Account/Folder.swift b/Account/Sources/Account/Folder.swift index ff311a008..4c27f1248 100644 --- a/Account/Sources/Account/Folder.swift +++ b/Account/Sources/Account/Folder.swift @@ -74,7 +74,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun // MARK: - Notifications - @objc func unreadCountDidChange(_ note: Notification) { + @MainActor @objc func unreadCountDidChange(_ note: Notification) { if let object = note.object { if objectIsChild(object as AnyObject) { updateUnreadCount() @@ -82,7 +82,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun } } - @objc func childrenDidChange(_ note: Notification) { + @MainActor @objc func childrenDidChange(_ note: Notification) { updateUnreadCount() } @@ -144,7 +144,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun private extension Folder { - func updateUnreadCount() { + @MainActor func updateUnreadCount() { var updatedUnreadCount = 0 for feed in topLevelFeeds { updatedUnreadCount += feed.unreadCount diff --git a/Account/Sources/Account/UnreadCountProvider.swift b/Account/Sources/Account/UnreadCountProvider.swift index f6bc48d4a..de9069c94 100644 --- a/Account/Sources/Account/UnreadCountProvider.swift +++ b/Account/Sources/Account/UnreadCountProvider.swift @@ -15,24 +15,24 @@ public extension Notification.Name { public protocol UnreadCountProvider { - var unreadCount: Int { get } + @MainActor var unreadCount: Int { get } - func postUnreadCountDidChangeNotification() - func calculateUnreadCount(_ children: T) -> Int + @MainActor func postUnreadCountDidChangeNotification() + @MainActor func calculateUnreadCount(_ children: T) -> Int } public extension UnreadCountProvider { - func postUnreadCountDidInitializeNotification() { + @MainActor func postUnreadCountDidInitializeNotification() { NotificationCenter.default.post(name: .UnreadCountDidInitialize, object: self, userInfo: nil) } - func postUnreadCountDidChangeNotification() { + @MainActor func postUnreadCountDidChangeNotification() { NotificationCenter.default.post(name: .UnreadCountDidChange, object: self, userInfo: nil) } - func calculateUnreadCount(_ children: T) -> Int { + @MainActor func calculateUnreadCount(_ children: T) -> Int { let updatedUnreadCount = children.reduce(0) { (result, oneChild) -> Int in if let oneUnreadCountProvider = oneChild as? UnreadCountProvider { return result + oneUnreadCountProvider.unreadCount diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 9c1bea221..96dd47f29 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -13,8 +13,9 @@ import Articles import Core protocol DetailWebViewControllerDelegate: AnyObject { - func mouseDidEnter(_: DetailWebViewController, link: String) - func mouseDidExit(_: DetailWebViewController) + + @MainActor func mouseDidEnter(_: DetailWebViewController, link: String) + @MainActor func mouseDidExit(_: DetailWebViewController) } final class DetailWebViewController: NSViewController { diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 078d02992..928deb5cf 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -13,7 +13,8 @@ import Core // MARK: - AccountsPreferencesAddAccountDelegate protocol AccountsPreferencesAddAccountDelegate { - func presentSheetForAccount(_ accountType: AccountType) + + @MainActor func presentSheetForAccount(_ accountType: AccountType) } // MARK: - AccountsPreferencesViewController diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift index 8c31e855c..f9d613de1 100644 --- a/Shared/ArticleStyles/ArticleThemesManager.swift +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -15,7 +15,7 @@ public extension Notification.Name { final class ArticleThemesManager: NSObject, NSFilePresenter { - static var shared: ArticleThemesManager! + @MainActor static var shared: ArticleThemesManager! public let folderPath: String lazy var presentedItemOperationQueue = OperationQueue.main