diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 1391e34b2..24d20e47e 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -74,7 +74,9 @@ final class CloudKitAccountDelegate: AccountDelegate { op.completionBlock = { mainThreadOperaion in completion() } - mainThreadOperationQueue.add(op) + Task { @MainActor in + mainThreadOperationQueue.add(op) + } } func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { @@ -124,7 +126,9 @@ final class CloudKitAccountDelegate: AccountDelegate { completion(.success(())) } } - mainThreadOperationQueue.add(op) + Task { @MainActor in + mainThreadOperationQueue.add(op) + } } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { @@ -777,7 +781,9 @@ private extension CloudKitAccountDelegate { completion(.success(())) } } - mainThreadOperationQueue.add(op) + Task { @MainActor in + mainThreadOperationQueue.add(op) + } } diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index 68654e582..224428b2b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -108,7 +108,7 @@ final class FeedlyAccountDelegate: AccountDelegate { completion() } - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + @MainActor func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { assert(Thread.isMainThread) guard currentSyncAllOperation == nil else { @@ -145,7 +145,7 @@ final class FeedlyAccountDelegate: AccountDelegate { operationQueue.add(syncAllOperation) } - func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + @MainActor func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { sendArticleStatus(for: account) { result in switch result { case .success: @@ -163,7 +163,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + @MainActor func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { // Ensure remote articles have the same status as they do locally. let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log) send.completionBlock = { operation in @@ -181,7 +181,7 @@ final class FeedlyAccountDelegate: AccountDelegate { /// /// - Parameter account: The account whose articles have a remote status. /// - Parameter completion: Call on the main queue. - func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + @MainActor func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { guard let credentials = credentials else { return completion(.success(())) } @@ -314,8 +314,8 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { - + @MainActor func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + do { guard let credentials = credentials else { throw FeedlyAccountDelegateError.notLoggedIn @@ -374,8 +374,8 @@ final class FeedlyAccountDelegate: AccountDelegate { feed.editedName = name } - func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { - + @MainActor func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { + do { guard let credentials = credentials else { throw FeedlyAccountDelegateError.notLoggedIn @@ -425,7 +425,7 @@ final class FeedlyAccountDelegate: AccountDelegate { folder.removeFeed(feed) } - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + @MainActor func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { guard let from = from as? Folder, let to = to as? Folder else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder)) @@ -458,7 +458,7 @@ final class FeedlyAccountDelegate: AccountDelegate { to.addFeed(feed) } - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { + @MainActor func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { if let existingFeed = account.existingFeed(withURL: feed.url) { account.addFeed(existingFeed, to: container) { result in switch result { @@ -480,7 +480,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { + @MainActor func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { let group = DispatchGroup() for feed in folder.topLevelFeeds { @@ -516,10 +516,12 @@ final class FeedlyAccountDelegate: AccountDelegate { self.database.insertStatuses(syncStatuses) { _ in self.database.selectPendingCount { result in - if let count = try? result.get(), count > 100 { - self.sendArticleStatus(for: account) { _ in } + MainActor.assumeIsolated { + if let count = try? result.get(), count > 100 { + self.sendArticleStatus(for: account) { _ in } + } + completion(.success(())) } - completion(.success(())) } } case .failure(let error): @@ -533,7 +535,7 @@ final class FeedlyAccountDelegate: AccountDelegate { credentials = try? account.retrieveCredentials(type: .oauthAccessToken) } - func accountWillBeDeleted(_ account: Account) { + @MainActor func accountWillBeDeleted(_ account: Account) { let logout = FeedlyLogoutOperation(account: account, service: caller, log: log) // Dispatch on the shared queue because the lifetime of the account delegate is uncertain. MainThreadOperationQueue.shared.add(logout) @@ -598,6 +600,8 @@ extension FeedlyAccountDelegate: FeedlyAPICallerDelegate { completionHandler(refreshAccessTokenDelegate.didReauthorize && !operation.isCanceled) } - MainThreadOperationQueue.shared.add(refreshAccessToken) + Task { @MainActor in + MainThreadOperationQueue.shared.add(refreshAccessToken) + } } } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift index 0a872360b..a36feb27b 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift @@ -17,7 +17,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, private let operationQueue = MainThreadOperationQueue() var addCompletionHandler: ((Result) -> ())? - init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog, customFeedName: String? = nil) throws { + @MainActor init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog, customFeedName: String? = nil) throws { let validator = FeedlyFeedContainerValidator(container: container) let (folder, collectionId) = try validator.getValidContainer() diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift index f1dd6c3c4..faad62bf6 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift @@ -30,8 +30,8 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl private var feedResourceId: FeedlyFeedResourceId? var addCompletionHandler: ((Result) -> ())? - init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { - + @MainActor init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { + let validator = FeedlyFeedContainerValidator(container: container) (self.folder, self.collectionId) = try validator.getValidContainer() @@ -75,7 +75,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl super.didFinish(with: error) } - func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) { + @MainActor func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) { guard !isCanceled else { return } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift index 1cd537b82..990336b83 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift @@ -21,7 +21,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation { private let operationQueue = MainThreadOperationQueue() private let finishOperation: FeedlyCheckpointOperation - init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) { + @MainActor init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) { self.account = account self.operationQueue.suspend() self.missingArticleEntryIdProvider = missingArticleEntryIdProvider @@ -43,27 +43,29 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation { let feedlyAPILimitBatchSize = 1000 for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) { - let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds)) - let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log) - getEntries.delegate = self - self.operationQueue.add(getEntries) - - let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, - parsedItemProvider: getEntries, - log: log) - organiseByFeed.delegate = self - organiseByFeed.addDependency(getEntries) - self.operationQueue.add(organiseByFeed) - - let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, - organisedItemsProvider: organiseByFeed, - log: log) - - updateAccount.delegate = self - updateAccount.addDependency(organiseByFeed) - self.operationQueue.add(updateAccount) + Task { @MainActor in + let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds)) + let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log) + getEntries.delegate = self + self.operationQueue.add(getEntries) - finishOperation.addDependency(updateAccount) + let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, + parsedItemProvider: getEntries, + log: log) + organiseByFeed.delegate = self + organiseByFeed.addDependency(getEntries) + self.operationQueue.add(organiseByFeed) + + let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, + organisedItemsProvider: organiseByFeed, + log: log) + + updateAccount.delegate = self + updateAccount.addDependency(organiseByFeed) + self.operationQueue.add(updateAccount) + + finishOperation.addDependency(updateAccount) + } } operationQueue.resume() diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift index 7920a133e..99d085171 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift @@ -34,7 +34,7 @@ final class FeedlySyncAllOperation: FeedlyOperation { /// /// Download articles for statuses at the union of those statuses without its corresponding article and those included in 3 (changed since last successful sync). /// - init(account: Account, feedlyUserId: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) { + @MainActor init(account: Account, feedlyUserId: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) { self.syncUUID = UUID() self.log = log self.operationQueue.suspend() @@ -126,7 +126,7 @@ final class FeedlySyncAllOperation: FeedlyOperation { self.operationQueue.add(finishOperation) } - convenience init(account: Account, feedlyUserId: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) { + @MainActor convenience init(account: Account, feedlyUserId: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) { self.init(account: account, feedlyUserId: feedlyUserId, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log) } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift index 659106361..4de17867b 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift @@ -24,7 +24,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD private let log: OSLog private let finishOperation: FeedlyCheckpointOperation - init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) { + @MainActor init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) { self.account = account self.resource = resource self.service = service @@ -41,7 +41,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD enqueueOperations(for: nil) } - convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) { + @MainActor convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) { let all = FeedlyCategoryResourceId.Global.all(for: credentials.username) self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log) } @@ -56,7 +56,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD super.didCancel() } - func enqueueOperations(for continuation: String?) { + @MainActor func enqueueOperations(for continuation: String?) { os_log(.debug, log: log, "Requesting page for %{public}@", resource.id) let operations = pageOperations(for: continuation) operationQueue.addOperations(operations) @@ -89,7 +89,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD return [getPage, organiseByFeed, updateAccount] } - func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) { + @MainActor func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) { guard !isCanceled else { os_log(.debug, log: log, "Cancelled requesting page for %{public}@", resource.id) return diff --git a/CloudKitExtras/Sources/CloudKitExtras/CloudKitError.swift b/CloudKitExtras/Sources/CloudKitExtras/CloudKitError.swift index 2d0e1ded7..a807e0cdc 100644 --- a/CloudKitExtras/Sources/CloudKitExtras/CloudKitError.swift +++ b/CloudKitExtras/Sources/CloudKitExtras/CloudKitError.swift @@ -10,7 +10,7 @@ import Foundation import CloudKit -public class CloudKitError: LocalizedError { +public final class CloudKitError: LocalizedError { public let error: Error diff --git a/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift b/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift index 83b4361dc..eae42efb2 100644 --- a/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift +++ b/CloudKitExtras/Sources/CloudKitExtras/CloudKitZone.swift @@ -27,13 +27,13 @@ public enum CloudKitZoneError: LocalizedError { } } -public protocol CloudKitZoneDelegate: class { +public protocol CloudKitZoneDelegate: AnyObject { func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result) -> Void); } public typealias CloudKitRecordKey = (recordType: CKRecord.RecordType, recordID: CKRecord.ID) -public protocol CloudKitZone: class { +public protocol CloudKitZone: AnyObject { static var qualityOfService: QualityOfService { get } diff --git a/Core/Sources/Core/MainThreadOperation.swift b/Core/Sources/Core/MainThreadOperation.swift index 5f6fd5d0c..92a4f97c3 100644 --- a/Core/Sources/Core/MainThreadOperation.swift +++ b/Core/Sources/Core/MainThreadOperation.swift @@ -15,7 +15,7 @@ import Foundation /// When it’s canceled, it should do its best to stop /// doing whatever it’s doing. However, it should not /// leave data in an inconsistent state. -public protocol MainThreadOperation: class { +public protocol MainThreadOperation: AnyObject { // These three properties are set by MainThreadOperationQueue. Don’t set them. var isCanceled: Bool { get set } // Check this at appropriate times in case the operation has been canceled. diff --git a/Core/Sources/Core/MainThreadOperationQueue.swift b/Core/Sources/Core/MainThreadOperationQueue.swift index 36455586e..cb1e091bc 100644 --- a/Core/Sources/Core/MainThreadOperationQueue.swift +++ b/Core/Sources/Core/MainThreadOperationQueue.swift @@ -8,7 +8,7 @@ import Foundation -public protocol MainThreadOperationDelegate: class { +public protocol MainThreadOperationDelegate: AnyObject { func operationDidComplete(_ operation: MainThreadOperation) func cancelOperation(_ operation: MainThreadOperation) func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation) @@ -26,14 +26,14 @@ public protocol MainThreadOperationDelegate: class { public final class MainThreadOperationQueue { /// Use the shared queue when you don’t need to create a separate queue. - public static let shared: MainThreadOperationQueue = { + @MainActor public static let shared: MainThreadOperationQueue = { MainThreadOperationQueue() }() private var operations = [Int: MainThreadOperation]() private var pendingOperationIDs = [Int]() private var currentOperationID: Int? - private static var incrementingID = 0 + @MainActor private static var incrementingID = 0 private var isSuspended = false private let dependencies = MainThreadOperationDependencies() @@ -51,7 +51,7 @@ public final class MainThreadOperationQueue { } /// Add an operation to the queue. - public func add(_ operation: MainThreadOperation) { + @MainActor public func add(_ operation: MainThreadOperation) { precondition(Thread.isMainThread) operation.operationDelegate = self let operationID = ensureOperationID(operation) @@ -67,12 +67,12 @@ public final class MainThreadOperationQueue { /// Add multiple operations to the queue. /// This has the same effect as calling addOperation one-by-one. - public func addOperations(_ operations: [MainThreadOperation]) { + @MainActor public func addOperations(_ operations: [MainThreadOperation]) { operations.forEach{ add($0) } } /// Add a dependency. Do this *before* calling addOperation, since addOperation might run the operation right away. - public func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation) { + @MainActor public func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation) { precondition(Thread.isMainThread) let childOperationID = ensureOperationID(childOperation) let parentOperationID = ensureOperationID(parentOperation) @@ -91,7 +91,7 @@ public final class MainThreadOperationQueue { /// Cancel some operations. If any of them have dependent operations, /// those operations will be canceled also. - public func cancelOperations(_ operations: [MainThreadOperation]) { + @MainActor public func cancelOperations(_ operations: [MainThreadOperation]) { precondition(Thread.isMainThread) let operationIDsToCancel = operations.map{ ensureOperationID($0) } assert(allOperationIDsArePendingOrCurrent(operationIDsToCancel)) @@ -106,7 +106,7 @@ public final class MainThreadOperationQueue { /// /// This will cancel the current operation, not just pending operations, /// if it has the specified name. - public func cancelOperations(named name: String) { + @MainActor public func cancelOperations(named name: String) { precondition(Thread.isMainThread) guard let operationsToCancel = pendingAndCurrentOperations(named: name) else { return @@ -137,7 +137,7 @@ extension MainThreadOperationQueue: MainThreadOperationDelegate { operationDidFinish(operation) } - public func cancelOperation(_ operation: MainThreadOperation) { + @MainActor public func cancelOperation(_ operation: MainThreadOperation) { cancelOperations([operation]) } } @@ -227,13 +227,13 @@ private extension MainThreadOperationQueue { return !operation.isCanceled && !dependencies.operationIDIsBlockedByDependency(operation.id!) } - func createOperationID() -> Int { + @MainActor func createOperationID() -> Int { precondition(Thread.isMainThread) Self.incrementingID += 1 return Self.incrementingID } - func ensureOperationID(_ operation: MainThreadOperation) -> Int { + @MainActor func ensureOperationID(_ operation: MainThreadOperation) -> Int { if let operationID = operation.id { return operationID } diff --git a/Core/Sources/Core/UndoableCommand.swift b/Core/Sources/Core/UndoableCommand.swift index 1a79051b5..69251d645 100644 --- a/Core/Sources/Core/UndoableCommand.swift +++ b/Core/Sources/Core/UndoableCommand.swift @@ -8,19 +8,19 @@ import Foundation -public protocol UndoableCommand: class { +public protocol UndoableCommand: AnyObject { - var undoActionName: String { get } - var redoActionName: String { get } - var undoManager: UndoManager { get } + @MainActor var undoActionName: String { get } + @MainActor var redoActionName: String { get } + @MainActor var undoManager: UndoManager { get } - func perform() // must call registerUndo() - func undo() // must call registerRedo() + @MainActor func perform() // must call registerUndo() + @MainActor func undo() // must call registerRedo() } extension UndoableCommand { - public func registerUndo() { + @MainActor public func registerUndo() { undoManager.setActionName(undoActionName) undoManager.registerUndo(withTarget: self) { (target) in @@ -28,7 +28,7 @@ extension UndoableCommand { } } - public func registerRedo() { + @MainActor public func registerRedo() { undoManager.setActionName(redoActionName) undoManager.registerUndo(withTarget: self) { (target) in @@ -39,30 +39,30 @@ extension UndoableCommand { // Useful for view controllers. -public protocol UndoableCommandRunner: class { +public protocol UndoableCommandRunner: AnyObject { - var undoableCommands: [UndoableCommand] { get set } - var undoManager: UndoManager? { get } - - func runCommand(_ undoableCommand: UndoableCommand) - func clearUndoableCommands() + @MainActor var undoableCommands: [UndoableCommand] { get set } + @MainActor var undoManager: UndoManager? { get } + + @MainActor func runCommand(_ undoableCommand: UndoableCommand) + @MainActor func clearUndoableCommands() } public extension UndoableCommandRunner { - func runCommand(_ undoableCommand: UndoableCommand) { - + @MainActor func runCommand(_ undoableCommand: UndoableCommand) { + pushUndoableCommand(undoableCommand) undoableCommand.perform() } - func pushUndoableCommand(_ undoableCommand: UndoableCommand) { - + @MainActor func pushUndoableCommand(_ undoableCommand: UndoableCommand) { + undoableCommands += [undoableCommand] } - func clearUndoableCommands() { - + @MainActor func clearUndoableCommands() { + // Useful, for example, when timeline is reloaded and the list of articles changes. // Otherwise things like Redo Mark Read are ambiguous. // (Do they apply to the previous articles or to the current articles?)