From e867487031a8825c002cd4766e081d62b2ebd4fc Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 23 Mar 2024 16:26:10 -0700 Subject: [PATCH] Make BatchUpdate MainActor. --- .../Feedbin/FeedbinAccountDelegate.swift | 30 ++-- .../LocalAccount/LocalAccountDelegate.swift | 167 +++++++++--------- .../NewsBlurAccountDelegate+Internal.swift | 21 ++- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 10 +- Core/Sources/Core/BatchUpdate.swift | 4 +- Mac/Scriptability/Account+Scriptability.swift | 2 +- Mac/Scriptability/Folder+Scriptability.swift | 2 +- iOS/Timeline/MarkAsReadAlertController.swift | 2 +- 8 files changed, 125 insertions(+), 113 deletions(-) diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 8f3f3e27b..f30e1971b 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -675,22 +675,24 @@ private extension FeedbinAccountDelegate { self.refreshProgress.completeTask() self.forceExpireFolderFeedRelationship(account, tags) self.caller.retrieveTaggings { result in - switch result { - case .success(let taggings): - - BatchUpdate.shared.perform { - self.syncFolders(account, tags) - self.syncFeeds(account, subscriptions) - self.syncFeedFolderRelationship(account, taggings) - } - self.refreshProgress.completeTask() - completion(.success(())) - - case .failure(let error): - completion(.failure(error)) + MainActor.assumeIsolated { + switch result { + case .success(let taggings): + + BatchUpdate.shared.perform { + self.syncFolders(account, tags) + self.syncFeeds(account, subscriptions) + self.syncFeedFolderRelationship(account, taggings) + } + + self.refreshProgress.completeTask() + completion(.success(())) + + case .failure(let error): + completion(.failure(error)) + } } - } case .failure(let error): diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 434939c81..93ec83db1 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -81,45 +81,47 @@ final class LocalAccountDelegate: AccountDelegate { } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { - var fileData: Data? - - do { - fileData = try Data(contentsOf: opmlFile) - } catch { - completion(.failure(error)) - return - } - - guard let opmlData = fileData else { + + Task { @MainActor in + var fileData: Data? + + do { + fileData = try Data(contentsOf: opmlFile) + } catch { + completion(.failure(error)) + return + } + + guard let opmlData = fileData else { + completion(.success(())) + return + } + + let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) + var opmlDocument: RSOPMLDocument? + + do { + opmlDocument = try RSOPMLParser.parseOPML(with: parserData) + } catch { + completion(.failure(error)) + return + } + + guard let loadDocument = opmlDocument else { + completion(.success(())) + return + } + + guard let children = loadDocument.children else { + return + } + + BatchUpdate.shared.perform { + account.loadOPMLItems(children) + } + completion(.success(())) - return } - - let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - var opmlDocument: RSOPMLDocument? - - do { - opmlDocument = try RSOPMLParser.parseOPML(with: parserData) - } catch { - completion(.failure(error)) - return - } - - guard let loadDocument = opmlDocument else { - completion(.success(())) - return - } - - guard let children = loadDocument.children else { - return - } - - BatchUpdate.shared.perform { - account.loadOPMLItems(children) - } - - completion(.success(())) - } func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { @@ -233,57 +235,60 @@ private extension LocalAccountDelegate { func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { - // We need to use a batch update here because we need to assign add the feed to the - // container before the name has been downloaded. This will put it in the sidebar - // with an Untitled name if we don't delay it being added to the sidebar. - BatchUpdate.shared.start() - refreshProgress.addToNumberOfTasksAndRemaining(1) - FeedFinder.find(url: url) { result in - - switch result { - case .success(let feedSpecifiers): - guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), - let url = URL(string: bestFeedSpecifier.urlString) else { - self.refreshProgress.completeTask() - BatchUpdate.shared.end() - completion(.failure(AccountError.createErrorNotFound)) - return - } + Task { @MainActor in + // We need to use a batch update here because we need to assign add the feed to the + // container before the name has been downloaded. This will put it in the sidebar + // with an Untitled name if we don't delay it being added to the sidebar. + BatchUpdate.shared.start() + refreshProgress.addToNumberOfTasksAndRemaining(1) + FeedFinder.find(url: url) { result in - if account.hasFeed(withURL: bestFeedSpecifier.urlString) { - self.refreshProgress.completeTask() - BatchUpdate.shared.end() - completion(.failure(AccountError.createErrorAlreadySubscribed)) - return - } - - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress.completeTask() - - if let parsedFeed = parsedFeed { - let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil) - feed.editedName = editedName - container.addFeed(feed) - - account.update(feed, with: parsedFeed, {_ in + MainActor.assumeIsolated { + switch result { + case .success(let feedSpecifiers): + guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), + let url = URL(string: bestFeedSpecifier.urlString) else { + self.refreshProgress.completeTask() BatchUpdate.shared.end() - completion(.success(feed)) - }) - } else { + completion(.failure(AccountError.createErrorNotFound)) + return + } + + if account.hasFeed(withURL: bestFeedSpecifier.urlString) { + self.refreshProgress.completeTask() + BatchUpdate.shared.end() + completion(.failure(AccountError.createErrorAlreadySubscribed)) + return + } + + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress.completeTask() + + if let parsedFeed = parsedFeed { + let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil) + feed.editedName = editedName + container.addFeed(feed) + + account.update(feed, with: parsedFeed, {_ in + MainActor.assumeIsolated { + BatchUpdate.shared.end() + completion(.success(feed)) + } + }) + } else { + BatchUpdate.shared.end() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + + case .failure: BatchUpdate.shared.end() + self.refreshProgress.completeTask() completion(.failure(AccountError.createErrorNotFound)) } - } - - case .failure: - BatchUpdate.shared.end() - self.refreshProgress.completeTask() - completion(.failure(AccountError.createErrorNotFound)) } - } - } - } diff --git a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift index 9f9f61fe0..17ff0ca70 100644 --- a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift +++ b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift @@ -21,16 +21,19 @@ extension NewsBlurAccountDelegate { os_log(.debug, log: log, "Refreshing feeds...") caller.retrieveFeeds { result in - switch result { - case .success((let feeds, let folders)): - BatchUpdate.shared.perform { - self.syncFolders(account, folders) - self.syncFeeds(account, feeds) - self.syncFeedFolderRelationship(account, folders) + + MainActor.assumeIsolated { + switch result { + case .success((let feeds, let folders)): + BatchUpdate.shared.perform { + self.syncFolders(account, folders) + self.syncFeeds(account, feeds) + self.syncFeedFolderRelationship(account, folders) + } + completion(.success(())) + case .failure(let error): + completion(.failure(error)) } - completion(.success(())) - case .failure(let error): - completion(.failure(error)) } } } diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 26620a732..b382aef5b 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -689,10 +689,12 @@ private extension ReaderAPIAccountDelegate { self.refreshProgress.completeTask() switch result { case .success(let subscriptions): - BatchUpdate.shared.perform { - self.syncFolders(account, tags) - self.syncFeeds(account, subscriptions) - self.syncFeedFolderRelationship(account, subscriptions) + MainActor.assumeIsolated { + BatchUpdate.shared.perform { + self.syncFolders(account, tags) + self.syncFeeds(account, subscriptions) + self.syncFeedFolderRelationship(account, subscriptions) + } } completion(.success(())) case .failure(let error): diff --git a/Core/Sources/Core/BatchUpdate.swift b/Core/Sources/Core/BatchUpdate.swift index 1396eda22..f7f031445 100644 --- a/Core/Sources/Core/BatchUpdate.swift +++ b/Core/Sources/Core/BatchUpdate.swift @@ -19,10 +19,10 @@ public extension Notification.Name { } /// A class for batch updating. -public final class BatchUpdate { +@MainActor public final class BatchUpdate { /// The shared batch update object. - public static let shared = BatchUpdate() + @MainActor public static let shared = BatchUpdate() private var count = 0 diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index af6b36f47..7f2d0978b 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -67,7 +67,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta return self.classDescription as! NSScriptClassDescription } - func deleteElement(_ element:ScriptingObject) { + @MainActor func deleteElement(_ element:ScriptingObject) { if let scriptableFolder = element as? ScriptableFolder { BatchUpdate.shared.perform { account.removeFolder(scriptableFolder.folder) { result in diff --git a/Mac/Scriptability/Folder+Scriptability.swift b/Mac/Scriptability/Folder+Scriptability.swift index d0d739b31..76bb22df6 100644 --- a/Mac/Scriptability/Folder+Scriptability.swift +++ b/Mac/Scriptability/Folder+Scriptability.swift @@ -50,7 +50,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai return self.classDescription as! NSScriptClassDescription } - func deleteElement(_ element:ScriptingObject) { + @MainActor func deleteElement(_ element:ScriptingObject) { if let scriptableFeed = element as? ScriptableFeed { BatchUpdate.shared.perform { folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in } diff --git a/iOS/Timeline/MarkAsReadAlertController.swift b/iOS/Timeline/MarkAsReadAlertController.swift index 5bbce1406..a0fb3a27a 100644 --- a/iOS/Timeline/MarkAsReadAlertController.swift +++ b/iOS/Timeline/MarkAsReadAlertController.swift @@ -15,7 +15,7 @@ extension UIView: MarkAsReadAlertControllerSourceType {} extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {} -struct MarkAsReadAlertController { +@MainActor struct MarkAsReadAlertController { static func confirm(_ controller: UIViewController?, coordinator: SceneCoordinator?,