From f36b54f34c82d6debb364d330d6b9420758d24d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Fri, 30 Mar 2018 11:16:42 +0200 Subject: [PATCH 01/15] Expose CloudCore to Objective C --- Source/Classes/CloudCore.swift | 8 ++++---- Source/Classes/Save/CoreDataListener.swift | 14 +++++++------- Source/Enum/Module.swift | 6 ++++-- Source/Protocols/CloudCoreDelegate.swift | 6 +++--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index 84421763..4e9ecb89 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -47,7 +47,7 @@ import CloudKit You can also check for updated data at CloudKit **manually** (e.g. push notifications are not working). Use for that `CloudCore.fetchAndSave(to:error:completion:)` */ -open class CloudCore { +@objc public class CloudCore: NSObject { // MARK: - Properties @@ -93,7 +93,7 @@ open class CloudCore { // Fetch updated data (e.g. push notifications weren't received) let updateFromCloudOperation = FetchAndSaveOperation(persistentContainer: container) updateFromCloudOperation.errorBlock = { - self.delegate?.error(error: $0, module: .some(.fetchFromCloud)) + self.delegate?.error(error: $0, module: .fetchFromCloud) } #if !os(watchOS) @@ -185,7 +185,7 @@ open class CloudCore { static private func handle(subscriptionError: Error, container: NSPersistentContainer) { guard let cloudError = subscriptionError as? CKError, let partialErrorValues = cloudError.partialErrorsByItemID?.values else { - delegate?.error(error: subscriptionError, module: nil) + delegate?.error(error: subscriptionError, module: .subscribeToCloud) return } @@ -203,7 +203,7 @@ open class CloudCore { } } - delegate?.error(error: subscriptionError, module: nil) + delegate?.error(error: subscriptionError, module: .subscribeToCloud) } } diff --git a/Source/Classes/Save/CoreDataListener.swift b/Source/Classes/Save/CoreDataListener.swift index 7410706f..989ef590 100644 --- a/Source/Classes/Save/CoreDataListener.swift +++ b/Source/Classes/Save/CoreDataListener.swift @@ -25,7 +25,7 @@ class CoreDataListener { public init(container: NSPersistentContainer) { self.container = container converter.errorBlock = { [weak self] in - self?.delegate?.error(error: $0, module: .some(.saveToCloud)) + self?.delegate?.error(error: $0, module: .saveToCloud) } } @@ -82,7 +82,7 @@ class CoreDataListener { try backgroundContext.save() } } catch { - listener.delegate?.error(error: error, module: .some(.saveToCloud)) + listener.delegate?.error(error: error, module: .saveToCloud) } CloudCore.delegate?.didSyncToCloud() @@ -91,7 +91,7 @@ class CoreDataListener { private func handle(error: Error, parentContext: NSManagedObjectContext) { guard let cloudError = error as? CKError else { - delegate?.error(error: error, module: .some(.saveToCloud)) + delegate?.error(error: error, module: .saveToCloud) return } @@ -103,25 +103,25 @@ class CoreDataListener { // Create CloudCore Zone let createZoneOperation = CreateCloudCoreZoneOperation() createZoneOperation.errorBlock = { - self.delegate?.error(error: $0, module: .some(.saveToCloud)) + self.delegate?.error(error: $0, module: .saveToCloud) self.cloudSaveOperationQueue.cancelAllOperations() } // Subscribe operation #if !os(watchOS) let subscribeOperation = SubscribeOperation() - subscribeOperation.errorBlock = { self.delegate?.error(error: $0, module: .some(.saveToCloud)) } + subscribeOperation.errorBlock = { self.delegate?.error(error: $0, module: .saveToCloud) } subscribeOperation.addDependency(createZoneOperation) cloudSaveOperationQueue.addOperation(subscribeOperation) #endif // Upload all local data let uploadOperation = UploadAllLocalDataOperation(parentContext: parentContext, managedObjectModel: container.managedObjectModel) - uploadOperation.errorBlock = { self.delegate?.error(error: $0, module: .some(.saveToCloud)) } + uploadOperation.errorBlock = { self.delegate?.error(error: $0, module: .saveToCloud) } cloudSaveOperationQueue.addOperations([createZoneOperation, uploadOperation], waitUntilFinished: true) case .operationCancelled: return - default: delegate?.error(error: cloudError, module: .some(.saveToCloud)) + default: delegate?.error(error: cloudError, module: .saveToCloud) } } diff --git a/Source/Enum/Module.swift b/Source/Enum/Module.swift index 2ff7525b..712dbb36 100644 --- a/Source/Enum/Module.swift +++ b/Source/Enum/Module.swift @@ -9,12 +9,14 @@ import Foundation /// Enumeration with module name that issued an error in `CloudCoreErrorDelegate` -public enum Module { +@objc public enum Module: Int { /// Save to CloudKit module case saveToCloud /// Fetch from CloudKit module case fetchFromCloud - + + /// No CloudKit module + case subscribeToCloud } diff --git a/Source/Protocols/CloudCoreDelegate.swift b/Source/Protocols/CloudCoreDelegate.swift index d0f28cf0..004d2b29 100644 --- a/Source/Protocols/CloudCoreDelegate.swift +++ b/Source/Protocols/CloudCoreDelegate.swift @@ -11,7 +11,7 @@ import Foundation /// Delegate for framework that can be used for proccesses tracking and error handling. /// Maybe usefull to activate `UIApplication.networkActivityIndicatorVisible`. /// All methods are optional. -public protocol CloudCoreDelegate: class { +@objc public protocol CloudCoreDelegate: class { // MARK: Notifications @@ -34,7 +34,7 @@ public protocol CloudCoreDelegate: class { /// - Parameters: /// - error: in most cases contains `CloudCoreError` or `CKError` /// - module: framework's module that throwed an error - func error(error: Error, module: Module?) + func error(error: Error, module: Module) } @@ -44,6 +44,6 @@ public extension CloudCoreDelegate { func didSyncFromCloud() { } func willSyncToCloud() { } func didSyncToCloud() { } - func error(error: Error, module: Module?) { } + func error(error: Error, module: Module) { } } From 7a3d00340e91e5e3f2a13a95883b1d5d01edf35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Fri, 30 Mar 2018 11:43:09 +0200 Subject: [PATCH 02/15] Remove unnecessary @objc public for CloudCore --- Source/Classes/CloudCore.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index 4e9ecb89..66544c32 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -47,7 +47,7 @@ import CloudKit You can also check for updated data at CloudKit **manually** (e.g. push notifications are not working). Use for that `CloudCore.fetchAndSave(to:error:completion:)` */ -@objc public class CloudCore: NSObject { +open class CloudCore: NSObject { // MARK: - Properties From b1ab74149892c6f4b955c2481652ee38d28e8508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Fri, 30 Mar 2018 11:46:23 +0200 Subject: [PATCH 03/15] Fix indentation in Module --- Source/Enum/Module.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Enum/Module.swift b/Source/Enum/Module.swift index 712dbb36..5eacae59 100644 --- a/Source/Enum/Module.swift +++ b/Source/Enum/Module.swift @@ -11,11 +11,11 @@ import Foundation /// Enumeration with module name that issued an error in `CloudCoreErrorDelegate` @objc public enum Module: Int { - /// Save to CloudKit module - case saveToCloud - - /// Fetch from CloudKit module - case fetchFromCloud + /// Save to CloudKit module + case saveToCloud + + /// Fetch from CloudKit module + case fetchFromCloud /// No CloudKit module case subscribeToCloud From 712b99a6d72143ea7d2d7b5e2393b75113ef164c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Fri, 30 Mar 2018 12:16:22 +0200 Subject: [PATCH 04/15] Fix example for Objective C --- Example/Sources/Class/NotificationsObserver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Sources/Class/NotificationsObserver.swift b/Example/Sources/Class/NotificationsObserver.swift index 0cc34643..ac0cab7e 100644 --- a/Example/Sources/Class/NotificationsObserver.swift +++ b/Example/Sources/Class/NotificationsObserver.swift @@ -28,7 +28,7 @@ class CloudCoreDelegateHandler: CloudCoreDelegate { os_log("✅ Finished saving to iCloud", log: OSLog.default, type: .debug) } - func error(error: Error, module: Module?) { + func error(error: Error, module: Module) { print("⚠️ CloudCore error detected in module \(String(describing: module)): \(error)") } From 01b680af51f83285e481f48959c8fdc168823a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roz=C3=A9?= Date: Fri, 30 Mar 2018 22:32:25 +0200 Subject: [PATCH 05/15] Sync all properties every time --- .../Classes/Save/ObjectToRecord/ObjectToRecordOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Classes/Save/ObjectToRecord/ObjectToRecordOperation.swift b/Source/Classes/Save/ObjectToRecord/ObjectToRecordOperation.swift index 917200bd..b0c013e9 100644 --- a/Source/Classes/Save/ObjectToRecord/ObjectToRecordOperation.swift +++ b/Source/Classes/Save/ObjectToRecord/ObjectToRecordOperation.swift @@ -56,7 +56,7 @@ class ObjectToRecordOperation: Operation { throw CloudCoreError.coreData("Unable to find managed object for record: \(record)") } - let changedValues = managedObject.committedValues(forKeys: changedAttributes) + let changedValues = managedObject.committedValues(forKeys: nil) for (attributeName, value) in changedValues { if attributeName == serviceAttributeNames.recordData || attributeName == serviceAttributeNames.recordID { continue } From 0ed8ca545457208c838a08c3d091bf6bc5350dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roz=C3=A9?= Date: Sat, 9 Feb 2019 14:16:08 +0100 Subject: [PATCH 06/15] Handle incompatible version --- CloudCore.xcodeproj/project.pbxproj | 6 +++++- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../xcshareddata/xcschemes/CloudCore.xcscheme | 8 +++----- .../RecordToCoreDataOperation.swift | 19 +++++++++++++------ Source/Enum/CloudCoreError.swift | 7 +++++-- Source/Extensions/NSManagedObject.swift | 1 + Source/Model/CloudCoreConfig.swift | 5 +++++ Source/Model/ServiceAttributeName.swift | 1 + Source/Model/Tokens.swift | 2 +- 9 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 CloudCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/CloudCore.xcodeproj/project.pbxproj b/CloudCore.xcodeproj/project.pbxproj index 6a51003b..bea438ea 100755 --- a/CloudCore.xcodeproj/project.pbxproj +++ b/CloudCore.xcodeproj/project.pbxproj @@ -595,7 +595,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0910; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Vasily Ulianov"; TargetAttributes = { D5B2E89E1C3A780C00C0327D = { @@ -816,12 +816,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -874,12 +876,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/CloudCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CloudCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/CloudCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/CloudCore.xcodeproj/xcshareddata/xcschemes/CloudCore.xcscheme b/CloudCore.xcodeproj/xcshareddata/xcschemes/CloudCore.xcscheme index ac2a4ade..9fac0451 100644 --- a/CloudCore.xcodeproj/xcshareddata/xcschemes/CloudCore.xcscheme +++ b/CloudCore.xcodeproj/xcshareddata/xcschemes/CloudCore.xcscheme @@ -1,6 +1,6 @@ + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift index bbc99627..35229123 100644 --- a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift +++ b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift @@ -53,7 +53,12 @@ class RecordToCoreDataOperation: AsynchronousOperation { guard let serviceAttributes = NSEntityDescription.entity(forEntityName: entityName, in: context)?.serviceAttributeNames else { throw CloudCoreError.missingServiceAttributes(entityName: entityName) } - + if let recordVersion = record.value(forKey: ServiceAttributeNames.recordVersion) as? Int { + guard recordVersion <= CloudCore.config.databaseVersion else { + throw CloudCoreError.incompatibleVersion + } + } + // Try to find existing objects let fetchRequest = NSFetchRequest(entityName: entityName) fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@", record.recordID.encodedString) @@ -74,11 +79,13 @@ class RecordToCoreDataOperation: AsynchronousOperation { /// - recordDataAttributeName: attribute name containing recordData private func fill(object: NSManagedObject, entityName: String, serviceAttributeNames: ServiceAttributeNames, context: NSManagedObjectContext) throws { for key in record.allKeys() { - let recordValue = record.value(forKey: key) - - let attribute = CloudKitAttribute(value: recordValue, fieldName: key, entityName: entityName, serviceAttributes: serviceAttributeNames, context: context) - let coreDataValue = try attribute.makeCoreDataValue() - object.setValue(coreDataValue, forKey: key) + if key != ServiceAttributeNames.recordVersion { + let recordValue = record.value(forKey: key) + + let attribute = CloudKitAttribute(value: recordValue, fieldName: key, entityName: entityName, serviceAttributes: serviceAttributeNames, context: context) + let coreDataValue = try attribute.makeCoreDataValue() + object.setValue(coreDataValue, forKey: key) + } } // Set system headers diff --git a/Source/Enum/CloudCoreError.swift b/Source/Enum/CloudCoreError.swift index 70c4e98b..90350846 100644 --- a/Source/Enum/CloudCoreError.swift +++ b/Source/Enum/CloudCoreError.swift @@ -22,8 +22,10 @@ public enum CloudCoreError: Error, CustomStringConvertible { /// Custom error, description is placed inside associated value case custom(String) - - + + /// Incompatible record version error + case incompatibleVersion + /// CloudCore doesn't support relationships with `NSOrderedSet` type case orderedSetRelationshipIsNotSupported(NSRelationshipDescription) @@ -36,6 +38,7 @@ public enum CloudCoreError: Error, CustomStringConvertible { case .cloudKit(let text): return "iCloud error: \(text)" case .coreData(let text): return "Core Data error: \(text)" case .custom(let error): return error + case .incompatibleVersion: return "Incompatible version error" case .orderedSetRelationshipIsNotSupported(let relationship): return "Relationships with NSOrderedSet type are not supported. Error occured in: \(relationship)" } } diff --git a/Source/Extensions/NSManagedObject.swift b/Source/Extensions/NSManagedObject.swift index 757781ed..6eefba9f 100644 --- a/Source/Extensions/NSManagedObject.swift +++ b/Source/Extensions/NSManagedObject.swift @@ -39,6 +39,7 @@ extension NSManagedObject { let record = CKRecord(recordType: entityName, zoneID: CloudCore.config.zoneID) self.setValue(record.encdodedSystemFields, forKey: serviceAttributeNames.recordData) self.setValue(record.recordID.encodedString, forKey: serviceAttributeNames.recordID) + self.setValue(CloudCore.config.databaseVersion, forKey: ServiceAttributeNames.recordVersion) return record } diff --git a/Source/Model/CloudCoreConfig.swift b/Source/Model/CloudCoreConfig.swift index 582fc48e..de18df0d 100644 --- a/Source/Model/CloudCoreConfig.swift +++ b/Source/Model/CloudCoreConfig.swift @@ -43,6 +43,11 @@ public struct CloudCoreConfig { /// subscriptionID's prefix for custom CKSubscription in public databases var publicSubscriptionIDPrefix = "CloudCore-" + + /// Database version to handle incompatible record version + /// + /// Default value is 1 + public var databaseVersion = 1 // MARK: Core Data let contextName = "CloudCoreFetchAndSave" diff --git a/Source/Model/ServiceAttributeName.swift b/Source/Model/ServiceAttributeName.swift index 7174eaf4..1b070730 100644 --- a/Source/Model/ServiceAttributeName.swift +++ b/Source/Model/ServiceAttributeName.swift @@ -15,6 +15,7 @@ struct ServiceAttributeNames { static let valueRecordData = "recordData" static let valueRecordID = "recordID" + static let recordVersion: String = "recordVersion" let entityName: String let recordData: String diff --git a/Source/Model/Tokens.swift b/Source/Model/Tokens.swift index a91ab8a8..dbc20f1b 100644 --- a/Source/Model/Tokens.swift +++ b/Source/Model/Tokens.swift @@ -45,7 +45,7 @@ open class Tokens: NSObject, NSCoding { /// Load saved Tokens from UserDefaults. Key is used from `CloudCoreConfig.userDefaultsKeyTokens` /// /// - Returns: previously saved `Token` object, if tokens weren't saved before newly initialized `Tokens` object will be returned - open static func loadFromUserDefaults() -> Tokens { + public static func loadFromUserDefaults() -> Tokens { guard let tokensData = UserDefaults.standard.data(forKey: CloudCore.config.userDefaultsKeyTokens), let tokens = NSKeyedUnarchiver.unarchiveObject(with: tokensData) as? Tokens else { return Tokens() From 447f677726ac44d190eae3dd351c320565241e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sat, 9 Feb 2019 14:57:40 +0100 Subject: [PATCH 07/15] Don't update token in case of fetch error --- .../SubOperations/FetchRecordZoneChangesOperation.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift index 67f0a407..00021e24 100644 --- a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift +++ b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift @@ -60,11 +60,12 @@ class FetchRecordZoneChangesOperation: Operation { self.recordWithIDWasDeletedBlock?(recordID) } fetchOperation.recordZoneFetchCompletionBlock = { zoneId, serverChangeToken, clientChangeTokenData, isMore, error in - self.tokens.tokensByRecordZoneID[zoneId] = serverChangeToken if let error = error { self.errorBlock?(zoneId, error) - } + } else { + self.tokens.tokensByRecordZoneID[zoneId] = serverChangeToken + } if isMore { let moreOperation = self.makeFetchOperation(optionsByRecordZoneID: optionsByRecordZoneID) From fa15c65fef2dc0c9da90d829206384944cc83fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sat, 9 Feb 2019 15:16:11 +0100 Subject: [PATCH 08/15] disable token saving if incompatible version occurred --- Source/Classes/CloudCore.swift | 4 +++- .../Fetch/SubOperations/RecordToCoreDataOperation.swift | 1 + Source/Model/Tokens.swift | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index 66544c32..e5317d0e 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -134,7 +134,9 @@ open class CloudCore: NSObject { DispatchQueue.global(qos: .utility).async { let errorProxy = ErrorBlockProxy(destination: error) let operation = FetchAndSaveOperation(from: [cloudDatabase], persistentContainer: container) - operation.errorBlock = { errorProxy.send(error: $0) } + operation.errorBlock = { + errorProxy.send(error: $0) + } operation.start() if errorProxy.wasError { diff --git a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift index 35229123..d5187d44 100644 --- a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift +++ b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift @@ -55,6 +55,7 @@ class RecordToCoreDataOperation: AsynchronousOperation { } if let recordVersion = record.value(forKey: ServiceAttributeNames.recordVersion) as? Int { guard recordVersion <= CloudCore.config.databaseVersion else { + CloudCore.tokens.canSaveToken = false throw CloudCoreError.incompatibleVersion } } diff --git a/Source/Model/Tokens.swift b/Source/Model/Tokens.swift index dbc20f1b..c3723819 100644 --- a/Source/Model/Tokens.swift +++ b/Source/Model/Tokens.swift @@ -30,6 +30,7 @@ import CloudKit open class Tokens: NSObject, NSCoding { var tokensByRecordZoneID = [CKRecordZoneID: CKServerChangeToken]() + var canSaveToken = true private struct ArchiverKey { static let tokensByRecordZoneID = "tokensByRecordZoneID" @@ -56,6 +57,10 @@ open class Tokens: NSObject, NSCoding { /// Save tokens to UserDefaults and synchronize. Key is used from `CloudCoreConfig.userDefaultsKeyTokens` open func saveToUserDefaults() { + guard canSaveToken else { + NSLog("CloudCore will not save tokens as incompatible version error occured") + return + } let tokensData = NSKeyedArchiver.archivedData(withRootObject: self) UserDefaults.standard.set(tokensData, forKey: CloudCore.config.userDefaultsKeyTokens) UserDefaults.standard.synchronize() From c3a13abacb06429fbad67f1f7dc82f1ea0bed726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sun, 10 Feb 2019 11:43:17 +0100 Subject: [PATCH 09/15] Change quality of service of cloud operation --- Source/Classes/CloudCore.swift | 4 +--- .../Fetch/SubOperations/FetchRecordZoneChangesOperation.swift | 2 +- Source/Classes/Save/CloudSaveOperationQueue.swift | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index e5317d0e..66544c32 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -134,9 +134,7 @@ open class CloudCore: NSObject { DispatchQueue.global(qos: .utility).async { let errorProxy = ErrorBlockProxy(destination: error) let operation = FetchAndSaveOperation(from: [cloudDatabase], persistentContainer: container) - operation.errorBlock = { - errorProxy.send(error: $0) - } + operation.errorBlock = { errorProxy.send(error: $0) } operation.start() if errorProxy.wasError { diff --git a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift index 00021e24..872d0a02 100644 --- a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift +++ b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift @@ -73,7 +73,7 @@ class FetchRecordZoneChangesOperation: Operation { } } - fetchOperation.qualityOfService = self.qualityOfService + fetchOperation.qualityOfService = .userInitiated fetchOperation.database = self.database return fetchOperation diff --git a/Source/Classes/Save/CloudSaveOperationQueue.swift b/Source/Classes/Save/CloudSaveOperationQueue.swift index 583cde4d..e9ba9a0e 100644 --- a/Source/Classes/Save/CloudSaveOperationQueue.swift +++ b/Source/Classes/Save/CloudSaveOperationQueue.swift @@ -64,6 +64,7 @@ class CloudSaveOperationQueue: OperationQueue { } modifyOperation.database = database + modifyOperation.qualityOfService = .userInitiated self.addOperation(modifyOperation) } From a13db1259d7867b5260c5913ab4c13d7bdd3388b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sun, 10 Feb 2019 11:49:35 +0100 Subject: [PATCH 10/15] Expose database version setter --- Source/Classes/CloudCore.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index 66544c32..de74988c 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -112,6 +112,11 @@ open class CloudCore: NSObject { // FIXME: unsubscribe } + + /// Set Database Version + public static func setDatabaseVersion(_ version: Int) { + config.databaseVersion = version + } // MARK: Fetchers From a39ce4d56945020fe4409bb77c35635205d663bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sun, 10 Feb 2019 11:56:17 +0100 Subject: [PATCH 11/15] Incompatible version information --- .../Fetch/SubOperations/RecordToCoreDataOperation.swift | 2 +- Source/Enum/CloudCoreError.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift index d5187d44..5c5f7edc 100644 --- a/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift +++ b/Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift @@ -56,7 +56,7 @@ class RecordToCoreDataOperation: AsynchronousOperation { if let recordVersion = record.value(forKey: ServiceAttributeNames.recordVersion) as? Int { guard recordVersion <= CloudCore.config.databaseVersion else { CloudCore.tokens.canSaveToken = false - throw CloudCoreError.incompatibleVersion + throw CloudCoreError.incompatibleVersion(recordVersion) } } diff --git a/Source/Enum/CloudCoreError.swift b/Source/Enum/CloudCoreError.swift index 90350846..af19817b 100644 --- a/Source/Enum/CloudCoreError.swift +++ b/Source/Enum/CloudCoreError.swift @@ -24,7 +24,7 @@ public enum CloudCoreError: Error, CustomStringConvertible { case custom(String) /// Incompatible record version error - case incompatibleVersion + case incompatibleVersion(Int) /// CloudCore doesn't support relationships with `NSOrderedSet` type case orderedSetRelationshipIsNotSupported(NSRelationshipDescription) @@ -38,7 +38,7 @@ public enum CloudCoreError: Error, CustomStringConvertible { case .cloudKit(let text): return "iCloud error: \(text)" case .coreData(let text): return "Core Data error: \(text)" case .custom(let error): return error - case .incompatibleVersion: return "Incompatible version error" + case .incompatibleVersion(let version): return "Record version \(version) / Database version \(CloudCore.config.databaseVersion)" case .orderedSetRelationshipIsNotSupported(let relationship): return "Relationships with NSOrderedSet type are not supported. Error occured in: \(relationship)" } } From a96cc0bff5ec19857d32b1af5ad67c2e71471f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Sun, 10 Feb 2019 14:23:54 +0100 Subject: [PATCH 12/15] Update record version of updated object --- .../Classes/Save/ObjectToRecord/ObjectToRecordConverter.swift | 3 +++ Source/Extensions/NSManagedObject.swift | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Classes/Save/ObjectToRecord/ObjectToRecordConverter.swift b/Source/Classes/Save/ObjectToRecord/ObjectToRecordConverter.swift index aa3d9bf8..798962c8 100644 --- a/Source/Classes/Save/ObjectToRecord/ObjectToRecordConverter.swift +++ b/Source/Classes/Save/ObjectToRecord/ObjectToRecordConverter.swift @@ -51,6 +51,9 @@ class ObjectToRecordConverter { } else { recordWithSystemFields = try object.setRecordInformation() } + + // Update record version + recordWithSystemFields.setValue(CloudCore.config.databaseVersion, forKey: ServiceAttributeNames.recordVersion) var changedAttributes: [String]? diff --git a/Source/Extensions/NSManagedObject.swift b/Source/Extensions/NSManagedObject.swift index 6eefba9f..757781ed 100644 --- a/Source/Extensions/NSManagedObject.swift +++ b/Source/Extensions/NSManagedObject.swift @@ -39,7 +39,6 @@ extension NSManagedObject { let record = CKRecord(recordType: entityName, zoneID: CloudCore.config.zoneID) self.setValue(record.encdodedSystemFields, forKey: serviceAttributeNames.recordData) self.setValue(record.recordID.encodedString, forKey: serviceAttributeNames.recordID) - self.setValue(CloudCore.config.databaseVersion, forKey: ServiceAttributeNames.recordVersion) return record } From 7f6d5e4aae76091f658c4f9a1ce09ef7f7d9b0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Mon, 11 Feb 2019 13:13:59 +0100 Subject: [PATCH 13/15] Remove quality of service --- .../Fetch/SubOperations/FetchRecordZoneChangesOperation.swift | 2 +- Source/Classes/Save/CloudSaveOperationQueue.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift index 872d0a02..00021e24 100644 --- a/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift +++ b/Source/Classes/Fetch/SubOperations/FetchRecordZoneChangesOperation.swift @@ -73,7 +73,7 @@ class FetchRecordZoneChangesOperation: Operation { } } - fetchOperation.qualityOfService = .userInitiated + fetchOperation.qualityOfService = self.qualityOfService fetchOperation.database = self.database return fetchOperation diff --git a/Source/Classes/Save/CloudSaveOperationQueue.swift b/Source/Classes/Save/CloudSaveOperationQueue.swift index e9ba9a0e..583cde4d 100644 --- a/Source/Classes/Save/CloudSaveOperationQueue.swift +++ b/Source/Classes/Save/CloudSaveOperationQueue.swift @@ -64,7 +64,6 @@ class CloudSaveOperationQueue: OperationQueue { } modifyOperation.database = database - modifyOperation.qualityOfService = .userInitiated self.addOperation(modifyOperation) } From bf5458852d0644f4bc222dbfda160bbdfcc8140d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roz=C3=A9?= Date: Mon, 15 Apr 2019 17:44:34 +0200 Subject: [PATCH 14/15] swift version --- .gitignore | 1 + CloudCore.xcodeproj/project.pbxproj | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f1933103..6ee42f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ ## Build generated build/ DerivedData/ +.DS_Store ## Various settings *.pbxuser diff --git a/CloudCore.xcodeproj/project.pbxproj b/CloudCore.xcodeproj/project.pbxproj index bea438ea..857d7f4c 100755 --- a/CloudCore.xcodeproj/project.pbxproj +++ b/CloudCore.xcodeproj/project.pbxproj @@ -634,6 +634,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -857,7 +858,7 @@ ONLY_ACTIVE_ARCH = YES; SUPPORTED_PLATFORMS = "macosx watchsimulator watchos appletvsimulator appletvos iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -910,7 +911,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SUPPORTED_PLATFORMS = "macosx watchsimulator watchos appletvsimulator appletvos iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; From 040df27f5f9d7a6a4e96b60172b685cfcbebca19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Roze=CC=81?= Date: Mon, 15 Apr 2019 18:00:51 +0200 Subject: [PATCH 15/15] Swift 4 objc --- Source/Classes/CloudCore.swift | 2 +- Source/Model/Tokens.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Classes/CloudCore.swift b/Source/Classes/CloudCore.swift index de74988c..dea2efe6 100644 --- a/Source/Classes/CloudCore.swift +++ b/Source/Classes/CloudCore.swift @@ -47,7 +47,7 @@ import CloudKit You can also check for updated data at CloudKit **manually** (e.g. push notifications are not working). Use for that `CloudCore.fetchAndSave(to:error:completion:)` */ -open class CloudCore: NSObject { +@objc @objcMembers open class CloudCore: NSObject { // MARK: - Properties diff --git a/Source/Model/Tokens.swift b/Source/Model/Tokens.swift index c3723819..62781ac6 100644 --- a/Source/Model/Tokens.swift +++ b/Source/Model/Tokens.swift @@ -27,7 +27,7 @@ import CloudKit } ``` */ -open class Tokens: NSObject, NSCoding { +@objc @objcMembers open class Tokens: NSObject, NSCoding { var tokensByRecordZoneID = [CKRecordZoneID: CKServerChangeToken]() var canSaveToken = true