Fix numerous concurrency warnings.
This commit is contained in:
parent
5bf5a067ab
commit
fb0479f324
@ -74,8 +74,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||||||
op.completionBlock = { mainThreadOperaion in
|
op.completionBlock = { mainThreadOperaion in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
Task { @MainActor in
|
||||||
mainThreadOperationQueue.add(op)
|
mainThreadOperationQueue.add(op)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
guard refreshProgress.isComplete else {
|
guard refreshProgress.isComplete else {
|
||||||
@ -124,8 +126,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task { @MainActor in
|
||||||
mainThreadOperationQueue.add(op)
|
mainThreadOperationQueue.add(op)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
guard refreshProgress.isComplete else {
|
guard refreshProgress.isComplete else {
|
||||||
@ -777,8 +781,10 @@ private extension CloudKitAccountDelegate {
|
|||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task { @MainActor in
|
||||||
mainThreadOperationQueue.add(op)
|
mainThreadOperationQueue.add(op)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func removeFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
func removeFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
@ -108,7 +108,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
@MainActor func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
assert(Thread.isMainThread)
|
assert(Thread.isMainThread)
|
||||||
|
|
||||||
guard currentSyncAllOperation == nil else {
|
guard currentSyncAllOperation == nil else {
|
||||||
@ -145,7 +145,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
operationQueue.add(syncAllOperation)
|
operationQueue.add(syncAllOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncArticleStatus(for account: Account, completion: ((Result<Void, Error>) -> Void)? = nil) {
|
@MainActor func syncArticleStatus(for account: Account, completion: ((Result<Void, Error>) -> Void)? = nil) {
|
||||||
sendArticleStatus(for: account) { result in
|
sendArticleStatus(for: account) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
@ -163,7 +163,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
@MainActor func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||||
// Ensure remote articles have the same status as they do locally.
|
// Ensure remote articles have the same status as they do locally.
|
||||||
let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log)
|
let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log)
|
||||||
send.completionBlock = { operation in
|
send.completionBlock = { operation in
|
||||||
@ -181,7 +181,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
///
|
///
|
||||||
/// - Parameter account: The account whose articles have a remote status.
|
/// - Parameter account: The account whose articles have a remote status.
|
||||||
/// - Parameter completion: Call on the main queue.
|
/// - Parameter completion: Call on the main queue.
|
||||||
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
@MainActor func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||||
guard let credentials = credentials else {
|
guard let credentials = credentials else {
|
||||||
return completion(.success(()))
|
return completion(.success(()))
|
||||||
}
|
}
|
||||||
@ -314,7 +314,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
@MainActor func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
guard let credentials = credentials else {
|
guard let credentials = credentials else {
|
||||||
@ -374,7 +374,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
feed.editedName = name
|
feed.editedName = name
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
@MainActor func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
guard let credentials = credentials else {
|
guard let credentials = credentials else {
|
||||||
@ -425,7 +425,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
folder.removeFeed(feed)
|
folder.removeFeed(feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
@MainActor func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
guard let from = from as? Folder, let to = to as? Folder else {
|
guard let from = from as? Folder, let to = to as? Folder else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
|
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
|
||||||
@ -458,7 +458,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
to.addFeed(feed)
|
to.addFeed(feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
@MainActor func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||||
account.addFeed(existingFeed, to: container) { result in
|
account.addFeed(existingFeed, to: container) { result in
|
||||||
switch result {
|
switch result {
|
||||||
@ -480,7 +480,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
@MainActor func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
for feed in folder.topLevelFeeds {
|
for feed in folder.topLevelFeeds {
|
||||||
@ -516,12 +516,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
self.database.insertStatuses(syncStatuses) { _ in
|
self.database.insertStatuses(syncStatuses) { _ in
|
||||||
self.database.selectPendingCount { result in
|
self.database.selectPendingCount { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
if let count = try? result.get(), count > 100 {
|
if let count = try? result.get(), count > 100 {
|
||||||
self.sendArticleStatus(for: account) { _ in }
|
self.sendArticleStatus(for: account) { _ in }
|
||||||
}
|
}
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
@ -533,7 +535,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
|
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountWillBeDeleted(_ account: Account) {
|
@MainActor func accountWillBeDeleted(_ account: Account) {
|
||||||
let logout = FeedlyLogoutOperation(account: account, service: caller, log: log)
|
let logout = FeedlyLogoutOperation(account: account, service: caller, log: log)
|
||||||
// Dispatch on the shared queue because the lifetime of the account delegate is uncertain.
|
// Dispatch on the shared queue because the lifetime of the account delegate is uncertain.
|
||||||
MainThreadOperationQueue.shared.add(logout)
|
MainThreadOperationQueue.shared.add(logout)
|
||||||
@ -598,6 +600,8 @@ extension FeedlyAccountDelegate: FeedlyAPICallerDelegate {
|
|||||||
completionHandler(refreshAccessTokenDelegate.didReauthorize && !operation.isCanceled)
|
completionHandler(refreshAccessTokenDelegate.didReauthorize && !operation.isCanceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
MainThreadOperationQueue.shared.add(refreshAccessToken)
|
MainThreadOperationQueue.shared.add(refreshAccessToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
|
|||||||
private let operationQueue = MainThreadOperationQueue()
|
private let operationQueue = MainThreadOperationQueue()
|
||||||
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||||
|
|
||||||
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 validator = FeedlyFeedContainerValidator(container: container)
|
||||||
let (folder, collectionId) = try validator.getValidContainer()
|
let (folder, collectionId) = try validator.getValidContainer()
|
||||||
|
@ -30,7 +30,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
|||||||
private var feedResourceId: FeedlyFeedResourceId?
|
private var feedResourceId: FeedlyFeedResourceId?
|
||||||
var addCompletionHandler: ((Result<Feed, Error>) -> ())?
|
var addCompletionHandler: ((Result<Feed, Error>) -> ())?
|
||||||
|
|
||||||
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)
|
let validator = FeedlyFeedContainerValidator(container: container)
|
||||||
@ -75,7 +75,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
|||||||
super.didFinish(with: error)
|
super.didFinish(with: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) {
|
@MainActor func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
|||||||
private let operationQueue = MainThreadOperationQueue()
|
private let operationQueue = MainThreadOperationQueue()
|
||||||
private let finishOperation: FeedlyCheckpointOperation
|
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.account = account
|
||||||
self.operationQueue.suspend()
|
self.operationQueue.suspend()
|
||||||
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider
|
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider
|
||||||
@ -43,6 +43,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
|||||||
let feedlyAPILimitBatchSize = 1000
|
let feedlyAPILimitBatchSize = 1000
|
||||||
for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) {
|
for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) {
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds))
|
let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds))
|
||||||
let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log)
|
let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log)
|
||||||
getEntries.delegate = self
|
getEntries.delegate = self
|
||||||
@ -65,6 +66,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
|||||||
|
|
||||||
finishOperation.addDependency(updateAccount)
|
finishOperation.addDependency(updateAccount)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
operationQueue.resume()
|
operationQueue.resume()
|
||||||
}
|
}
|
||||||
|
@ -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).
|
/// 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.syncUUID = UUID()
|
||||||
self.log = log
|
self.log = log
|
||||||
self.operationQueue.suspend()
|
self.operationQueue.suspend()
|
||||||
@ -126,7 +126,7 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
|||||||
self.operationQueue.add(finishOperation)
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
|||||||
private let log: OSLog
|
private let log: OSLog
|
||||||
private let finishOperation: FeedlyCheckpointOperation
|
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.account = account
|
||||||
self.resource = resource
|
self.resource = resource
|
||||||
self.service = service
|
self.service = service
|
||||||
@ -41,7 +41,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
|||||||
enqueueOperations(for: nil)
|
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)
|
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||||
self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log)
|
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()
|
super.didCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func enqueueOperations(for continuation: String?) {
|
@MainActor func enqueueOperations(for continuation: String?) {
|
||||||
os_log(.debug, log: log, "Requesting page for %{public}@", resource.id)
|
os_log(.debug, log: log, "Requesting page for %{public}@", resource.id)
|
||||||
let operations = pageOperations(for: continuation)
|
let operations = pageOperations(for: continuation)
|
||||||
operationQueue.addOperations(operations)
|
operationQueue.addOperations(operations)
|
||||||
@ -89,7 +89,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
|||||||
return [getPage, organiseByFeed, updateAccount]
|
return [getPage, organiseByFeed, updateAccount]
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) {
|
@MainActor func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
os_log(.debug, log: log, "Cancelled requesting page for %{public}@", resource.id)
|
os_log(.debug, log: log, "Cancelled requesting page for %{public}@", resource.id)
|
||||||
return
|
return
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CloudKit
|
import CloudKit
|
||||||
|
|
||||||
public class CloudKitError: LocalizedError {
|
public final class CloudKitError: LocalizedError {
|
||||||
|
|
||||||
public let error: Error
|
public let error: Error
|
||||||
|
|
||||||
|
@ -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, Error>) -> Void);
|
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void);
|
||||||
}
|
}
|
||||||
|
|
||||||
public typealias CloudKitRecordKey = (recordType: CKRecord.RecordType, recordID: CKRecord.ID)
|
public typealias CloudKitRecordKey = (recordType: CKRecord.RecordType, recordID: CKRecord.ID)
|
||||||
|
|
||||||
public protocol CloudKitZone: class {
|
public protocol CloudKitZone: AnyObject {
|
||||||
|
|
||||||
static var qualityOfService: QualityOfService { get }
|
static var qualityOfService: QualityOfService { get }
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import Foundation
|
|||||||
/// When it’s canceled, it should do its best to stop
|
/// When it’s canceled, it should do its best to stop
|
||||||
/// doing whatever it’s doing. However, it should not
|
/// doing whatever it’s doing. However, it should not
|
||||||
/// leave data in an inconsistent state.
|
/// 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.
|
// 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.
|
var isCanceled: Bool { get set } // Check this at appropriate times in case the operation has been canceled.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol MainThreadOperationDelegate: class {
|
public protocol MainThreadOperationDelegate: AnyObject {
|
||||||
func operationDidComplete(_ operation: MainThreadOperation)
|
func operationDidComplete(_ operation: MainThreadOperation)
|
||||||
func cancelOperation(_ operation: MainThreadOperation)
|
func cancelOperation(_ operation: MainThreadOperation)
|
||||||
func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation)
|
func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation)
|
||||||
@ -26,14 +26,14 @@ public protocol MainThreadOperationDelegate: class {
|
|||||||
public final class MainThreadOperationQueue {
|
public final class MainThreadOperationQueue {
|
||||||
|
|
||||||
/// Use the shared queue when you don’t need to create a separate queue.
|
/// 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()
|
MainThreadOperationQueue()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var operations = [Int: MainThreadOperation]()
|
private var operations = [Int: MainThreadOperation]()
|
||||||
private var pendingOperationIDs = [Int]()
|
private var pendingOperationIDs = [Int]()
|
||||||
private var currentOperationID: Int?
|
private var currentOperationID: Int?
|
||||||
private static var incrementingID = 0
|
@MainActor private static var incrementingID = 0
|
||||||
private var isSuspended = false
|
private var isSuspended = false
|
||||||
private let dependencies = MainThreadOperationDependencies()
|
private let dependencies = MainThreadOperationDependencies()
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public final class MainThreadOperationQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add an operation to the queue.
|
/// Add an operation to the queue.
|
||||||
public func add(_ operation: MainThreadOperation) {
|
@MainActor public func add(_ operation: MainThreadOperation) {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
operation.operationDelegate = self
|
operation.operationDelegate = self
|
||||||
let operationID = ensureOperationID(operation)
|
let operationID = ensureOperationID(operation)
|
||||||
@ -67,12 +67,12 @@ public final class MainThreadOperationQueue {
|
|||||||
|
|
||||||
/// Add multiple operations to the queue.
|
/// Add multiple operations to the queue.
|
||||||
/// This has the same effect as calling addOperation one-by-one.
|
/// 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) }
|
operations.forEach{ add($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a dependency. Do this *before* calling addOperation, since addOperation might run the operation right away.
|
/// 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)
|
precondition(Thread.isMainThread)
|
||||||
let childOperationID = ensureOperationID(childOperation)
|
let childOperationID = ensureOperationID(childOperation)
|
||||||
let parentOperationID = ensureOperationID(parentOperation)
|
let parentOperationID = ensureOperationID(parentOperation)
|
||||||
@ -91,7 +91,7 @@ public final class MainThreadOperationQueue {
|
|||||||
|
|
||||||
/// Cancel some operations. If any of them have dependent operations,
|
/// Cancel some operations. If any of them have dependent operations,
|
||||||
/// those operations will be canceled also.
|
/// those operations will be canceled also.
|
||||||
public func cancelOperations(_ operations: [MainThreadOperation]) {
|
@MainActor public func cancelOperations(_ operations: [MainThreadOperation]) {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
let operationIDsToCancel = operations.map{ ensureOperationID($0) }
|
let operationIDsToCancel = operations.map{ ensureOperationID($0) }
|
||||||
assert(allOperationIDsArePendingOrCurrent(operationIDsToCancel))
|
assert(allOperationIDsArePendingOrCurrent(operationIDsToCancel))
|
||||||
@ -106,7 +106,7 @@ public final class MainThreadOperationQueue {
|
|||||||
///
|
///
|
||||||
/// This will cancel the current operation, not just pending operations,
|
/// This will cancel the current operation, not just pending operations,
|
||||||
/// if it has the specified name.
|
/// if it has the specified name.
|
||||||
public func cancelOperations(named name: String) {
|
@MainActor public func cancelOperations(named name: String) {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
guard let operationsToCancel = pendingAndCurrentOperations(named: name) else {
|
guard let operationsToCancel = pendingAndCurrentOperations(named: name) else {
|
||||||
return
|
return
|
||||||
@ -137,7 +137,7 @@ extension MainThreadOperationQueue: MainThreadOperationDelegate {
|
|||||||
operationDidFinish(operation)
|
operationDidFinish(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cancelOperation(_ operation: MainThreadOperation) {
|
@MainActor public func cancelOperation(_ operation: MainThreadOperation) {
|
||||||
cancelOperations([operation])
|
cancelOperations([operation])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,13 +227,13 @@ private extension MainThreadOperationQueue {
|
|||||||
return !operation.isCanceled && !dependencies.operationIDIsBlockedByDependency(operation.id!)
|
return !operation.isCanceled && !dependencies.operationIDIsBlockedByDependency(operation.id!)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOperationID() -> Int {
|
@MainActor func createOperationID() -> Int {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
Self.incrementingID += 1
|
Self.incrementingID += 1
|
||||||
return Self.incrementingID
|
return Self.incrementingID
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureOperationID(_ operation: MainThreadOperation) -> Int {
|
@MainActor func ensureOperationID(_ operation: MainThreadOperation) -> Int {
|
||||||
if let operationID = operation.id {
|
if let operationID = operation.id {
|
||||||
return operationID
|
return operationID
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,19 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol UndoableCommand: class {
|
public protocol UndoableCommand: AnyObject {
|
||||||
|
|
||||||
var undoActionName: String { get }
|
@MainActor var undoActionName: String { get }
|
||||||
var redoActionName: String { get }
|
@MainActor var redoActionName: String { get }
|
||||||
var undoManager: UndoManager { get }
|
@MainActor var undoManager: UndoManager { get }
|
||||||
|
|
||||||
func perform() // must call registerUndo()
|
@MainActor func perform() // must call registerUndo()
|
||||||
func undo() // must call registerRedo()
|
@MainActor func undo() // must call registerRedo()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UndoableCommand {
|
extension UndoableCommand {
|
||||||
|
|
||||||
public func registerUndo() {
|
@MainActor public func registerUndo() {
|
||||||
|
|
||||||
undoManager.setActionName(undoActionName)
|
undoManager.setActionName(undoActionName)
|
||||||
undoManager.registerUndo(withTarget: self) { (target) in
|
undoManager.registerUndo(withTarget: self) { (target) in
|
||||||
@ -28,7 +28,7 @@ extension UndoableCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func registerRedo() {
|
@MainActor public func registerRedo() {
|
||||||
|
|
||||||
undoManager.setActionName(redoActionName)
|
undoManager.setActionName(redoActionName)
|
||||||
undoManager.registerUndo(withTarget: self) { (target) in
|
undoManager.registerUndo(withTarget: self) { (target) in
|
||||||
@ -39,29 +39,29 @@ extension UndoableCommand {
|
|||||||
|
|
||||||
// Useful for view controllers.
|
// Useful for view controllers.
|
||||||
|
|
||||||
public protocol UndoableCommandRunner: class {
|
public protocol UndoableCommandRunner: AnyObject {
|
||||||
|
|
||||||
var undoableCommands: [UndoableCommand] { get set }
|
@MainActor var undoableCommands: [UndoableCommand] { get set }
|
||||||
var undoManager: UndoManager? { get }
|
@MainActor var undoManager: UndoManager? { get }
|
||||||
|
|
||||||
func runCommand(_ undoableCommand: UndoableCommand)
|
@MainActor func runCommand(_ undoableCommand: UndoableCommand)
|
||||||
func clearUndoableCommands()
|
@MainActor func clearUndoableCommands()
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension UndoableCommandRunner {
|
public extension UndoableCommandRunner {
|
||||||
|
|
||||||
func runCommand(_ undoableCommand: UndoableCommand) {
|
@MainActor func runCommand(_ undoableCommand: UndoableCommand) {
|
||||||
|
|
||||||
pushUndoableCommand(undoableCommand)
|
pushUndoableCommand(undoableCommand)
|
||||||
undoableCommand.perform()
|
undoableCommand.perform()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushUndoableCommand(_ undoableCommand: UndoableCommand) {
|
@MainActor func pushUndoableCommand(_ undoableCommand: UndoableCommand) {
|
||||||
|
|
||||||
undoableCommands += [undoableCommand]
|
undoableCommands += [undoableCommand]
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearUndoableCommands() {
|
@MainActor func clearUndoableCommands() {
|
||||||
|
|
||||||
// Useful, for example, when timeline is reloaded and the list of articles changes.
|
// Useful, for example, when timeline is reloaded and the list of articles changes.
|
||||||
// Otherwise things like Redo Mark Read are ambiguous.
|
// Otherwise things like Redo Mark Read are ambiguous.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user