From b9db9809bccdebd44c786c28c8b9f067661fed09 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 11:32:25 +0800 Subject: [PATCH 01/25] container.add -> database.add --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 61dabe8a..8a6ff692 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -62,7 +62,7 @@ extension DatabaseManager { modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in print("Resume modify records success!") } - self.container.add(modifyOp) + self.database.add(modifyOp) } }) } From f1f98ccd14f845d97b29e19c0cea7d4313afc87d Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 11:39:01 +0800 Subject: [PATCH 02/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20push=20=E9=83=A8?= =?UTF-8?q?=E5=88=86=20object=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 71e38444..2a3d8527 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -80,7 +80,12 @@ extension SyncEngine { public func pushAll() { databaseManager.syncObjects.forEach { $0.pushLocalObjectsToCloudKit() } } - + + public func push(objects: [CKRecordConvertible], completionHandler: ((Error?) -> Void)? = nil) { + let recordsToStore: [CKRecord] = objects.filter { !$0.isDeleted }.map { $0.record } + let recordsIDsToDelete: [CKRecord.ID] = objects.filter { $0.isDeleted }.map { $0.recordID } + databaseManager.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordsIDsToDelete, completion: completionHandler) + } } public enum Notifications: String, NotificationName { From fd447b29b7f01cc30131f0f56875a26aa1516028 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 12:28:59 +0800 Subject: [PATCH 03/25] =?UTF-8?q?pushAll=20=E9=80=89=E6=8B=A9=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 2a3d8527..d1a3875c 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -77,8 +77,9 @@ extension SyncEngine { /// Push all existing local data to CloudKit /// You should NOT to call this method too frequently - public func pushAll() { - databaseManager.syncObjects.forEach { $0.pushLocalObjectsToCloudKit() } + public func pushAll(objects: [Syncable]? = nil) { + let objects = objects?.filter(databaseManager.syncObjects.contains($0)) ?? databaseManager.syncObjects + objects.forEach { $0.pushLocalObjectsToCloudKit() } } public func push(objects: [CKRecordConvertible], completionHandler: ((Error?) -> Void)? = nil) { From 03508c8e70ede3d8cd0bfa980816be2fb191f26d Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 12:30:32 +0800 Subject: [PATCH 04/25] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index d1a3875c..513eaa04 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -78,8 +78,7 @@ extension SyncEngine { /// Push all existing local data to CloudKit /// You should NOT to call this method too frequently public func pushAll(objects: [Syncable]? = nil) { - let objects = objects?.filter(databaseManager.syncObjects.contains($0)) ?? databaseManager.syncObjects - objects.forEach { $0.pushLocalObjectsToCloudKit() } + (objects ?? databaseManager.syncObjects).forEach { $0.pushLocalObjectsToCloudKit() } } public func push(objects: [CKRecordConvertible], completionHandler: ((Error?) -> Void)? = nil) { From 057486ef7d95ea5cd38e4bc7fb799388efee5e17 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 12:36:58 +0800 Subject: [PATCH 05/25] =?UTF-8?q?=E8=BF=98=E6=98=AF=E4=B8=8D=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20pushAll=20=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 513eaa04..2a3d8527 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -77,8 +77,8 @@ extension SyncEngine { /// Push all existing local data to CloudKit /// You should NOT to call this method too frequently - public func pushAll(objects: [Syncable]? = nil) { - (objects ?? databaseManager.syncObjects).forEach { $0.pushLocalObjectsToCloudKit() } + public func pushAll() { + databaseManager.syncObjects.forEach { $0.pushLocalObjectsToCloudKit() } } public func push(objects: [CKRecordConvertible], completionHandler: ((Error?) -> Void)? = nil) { From 923bed31f46198d95807ef7278bba1bb8748b2a8 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 13:26:59 +0800 Subject: [PATCH 06/25] database -> container // https://developer.apple.com/documentation/cloudkit/ckoperation\#1666033 --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 8a6ff692..61dabe8a 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -62,7 +62,7 @@ extension DatabaseManager { modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in print("Resume modify records success!") } - self.database.add(modifyOp) + self.container.add(modifyOp) } }) } From 21cad102f13d1187f38b5c9fb190556878a5fa29 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 13:29:52 +0800 Subject: [PATCH 07/25] =?UTF-8?q?iOS=2015=20=E4=B8=8D=E5=86=8D=E5=A4=84?= =?UTF-8?q?=E7=90=86=20long=20lived=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/DatabaseManagerProtocol.swift | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 61dabe8a..8980830c 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -53,18 +53,22 @@ extension DatabaseManager { } func resumeLongLivedOperationIfPossible() { - container.fetchAllLongLivedOperationIDs { [weak self]( opeIDs, error) in - guard let self = self, error == nil, let ids = opeIDs else { return } - for id in ids { - self.container.fetchLongLivedOperation(withID: id, completionHandler: { [weak self](ope, error) in - guard let self = self, error == nil else { return } - if let modifyOp = ope as? CKModifyRecordsOperation { - modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in - print("Resume modify records success!") + if #available(iOS 15.0) { + // do nothing. + } else { + container.fetchAllLongLivedOperationIDs { [weak self]( opeIDs, error) in + guard let self = self, error == nil, let ids = opeIDs else { return } + for id in ids { + self.container.fetchLongLivedOperation(withID: id, completionHandler: { [weak self](ope, error) in + guard let self = self, error == nil else { return } + if let modifyOp = ope as? CKModifyRecordsOperation { + modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in + print("Resume modify records success!") + } + self.container.add(modifyOp) } - self.container.add(modifyOp) - } - }) + }) + } } } } From 148a908933f63c8ba45537e14196ff03f58376e0 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 29 Jul 2021 13:30:54 +0800 Subject: [PATCH 08/25] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 8980830c..e1da63ab 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -53,7 +53,7 @@ extension DatabaseManager { } func resumeLongLivedOperationIfPossible() { - if #available(iOS 15.0) { + if #available(iOS 15.0, *) { // do nothing. } else { container.fetchAllLongLivedOperationIDs { [weak self]( opeIDs, error) in From 4a4dbe858c1888a984db8d032727733f171f37de Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 26 Aug 2021 17:10:21 +0800 Subject: [PATCH 09/25] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=B6?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20pull=20=E7=9A=84=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 2a3d8527..e7f947a5 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -17,25 +17,25 @@ public final class SyncEngine { private let databaseManager: DatabaseManager - public convenience init(objects: [Syncable], databaseScope: CKDatabase.Scope = .private, container: CKContainer = .default()) { + public convenience init(objects: [Syncable], databaseScope: CKDatabase.Scope = .private, container: CKContainer = .default(), callback: ((Error?) -> Void)? = nil) { switch databaseScope { case .private: let privateDatabaseManager = PrivateDatabaseManager(objects: objects, container: container) - self.init(databaseManager: privateDatabaseManager) + self.init(databaseManager: privateDatabaseManager, callback: callback) case .public: let publicDatabaseManager = PublicDatabaseManager(objects: objects, container: container) - self.init(databaseManager: publicDatabaseManager) + self.init(databaseManager: publicDatabaseManager, callback: callback) default: fatalError("Shared database scope is not supported yet") } } - private init(databaseManager: DatabaseManager) { + private init(databaseManager: DatabaseManager, callback: ((Error?) -> Void)?) { self.databaseManager = databaseManager - setup() + setup(callback) } - private func setup() { + private func setup(_ callback: ((Error?) -> Void)?) { databaseManager.prepare() databaseManager.container.accountStatus { [weak self] (status, error) in guard let self = self else { return } @@ -43,20 +43,22 @@ public final class SyncEngine { case .available: self.databaseManager.registerLocalDatabase() self.databaseManager.createCustomZonesIfAllowed() - self.databaseManager.fetchChangesInDatabase(nil) + self.databaseManager.fetchChangesInDatabase(callback) self.databaseManager.resumeLongLivedOperationIfPossible() self.databaseManager.startObservingRemoteChanges() self.databaseManager.startObservingTermination() self.databaseManager.createDatabaseSubscriptionIfHaveNot() case .noAccount, .restricted: guard self.databaseManager is PublicDatabaseManager else { break } - self.databaseManager.fetchChangesInDatabase(nil) + self.databaseManager.fetchChangesInDatabase(callback) self.databaseManager.resumeLongLivedOperationIfPossible() self.databaseManager.startObservingRemoteChanges() self.databaseManager.startObservingTermination() self.databaseManager.createDatabaseSubscriptionIfHaveNot() case .couldNotDetermine: break + case .temporarilyUnavailable: + break @unknown default: break } From 24a73df6b053a51fd2b61f94bf1cf5240e54e8c8 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 2 Sep 2021 15:41:37 +0800 Subject: [PATCH 10/25] =?UTF-8?q?iOS=2015=20=E4=B8=8E=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8C=BA=E5=88=86=E5=A4=84=E7=90=86=20longLi?= =?UTF-8?q?veOperation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/DatabaseManagerProtocol.swift | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index e1da63ab..c0c19358 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -53,22 +53,22 @@ extension DatabaseManager { } func resumeLongLivedOperationIfPossible() { - if #available(iOS 15.0, *) { - // do nothing. - } else { - container.fetchAllLongLivedOperationIDs { [weak self]( opeIDs, error) in - guard let self = self, error == nil, let ids = opeIDs else { return } - for id in ids { - self.container.fetchLongLivedOperation(withID: id, completionHandler: { [weak self](ope, error) in - guard let self = self, error == nil else { return } - if let modifyOp = ope as? CKModifyRecordsOperation { - modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in - print("Resume modify records success!") - } + container.fetchAllLongLivedOperationIDs { [weak self]( opeIDs, error) in + guard let self = self, error == nil, let ids = opeIDs else { return } + for id in ids { + self.container.fetchLongLivedOperation(withID: id, completionHandler: { [weak self](ope, error) in + guard let self = self, error == nil else { return } + if let modifyOp = ope as? CKModifyRecordsOperation { + modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in + print("Resume modify records success!") + } + if #available(iOS 15, *) { + self.database.add(modifyOp) + } else { self.container.add(modifyOp) } - }) - } + } + }) } } } From f9c554575e7d3701cfc0244bb1d0505d7a280ef1 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 2 Sep 2021 15:44:15 +0800 Subject: [PATCH 11/25] =?UTF-8?q?=E5=8E=BB=E6=8E=89=20iOS=2015=20=E4=BB=A5?= =?UTF-8?q?=E5=89=8D=E6=B2=A1=E6=9C=89=E7=9A=84=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index e7f947a5..3a22e357 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -57,8 +57,6 @@ public final class SyncEngine { self.databaseManager.createDatabaseSubscriptionIfHaveNot() case .couldNotDetermine: break - case .temporarilyUnavailable: - break @unknown default: break } From b2ae85230b1499f4e57f5e1ddc0c4d44143d5261 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Tue, 7 Sep 2021 18:33:12 +0800 Subject: [PATCH 12/25] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=9A=82=E5=81=9C?= =?UTF-8?q?=E7=9B=91=E5=90=AC=20changes=20=E9=80=9A=E7=9F=A5=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 5 ++++- IceCream/Classes/PrivateDatabaseManager.swift | 18 +++++++++++++++--- IceCream/Classes/PublicDatabaseManager.swift | 12 +++++++++++- IceCream/Classes/SyncEngine.swift | 8 ++++++++ IceCream/Classes/SyncObject.swift | 6 ++++++ IceCream/Classes/Syncable.swift | 1 + 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index c0c19358..536630fe 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -37,6 +37,7 @@ protocol DatabaseManager: class { func startObservingTermination() func createDatabaseSubscriptionIfHaveNot() func registerLocalDatabase() + func unregisterLocalDatabase() func cleanUp() } @@ -63,6 +64,7 @@ extension DatabaseManager { print("Resume modify records success!") } if #available(iOS 15, *) { + print("Add operation: fetchLongLivedOperation") self.database.add(modifyOp) } else { self.container.add(modifyOp) @@ -131,7 +133,8 @@ extension DatabaseManager { return } } - + + print("Add operation: syncRecordsToCloudKit") database.add(modifyOpe) } diff --git a/IceCream/Classes/PrivateDatabaseManager.swift b/IceCream/Classes/PrivateDatabaseManager.swift index 071c5423..e8fb5e3a 100644 --- a/IceCream/Classes/PrivateDatabaseManager.swift +++ b/IceCream/Classes/PrivateDatabaseManager.swift @@ -60,7 +60,8 @@ final class PrivateDatabaseManager: DatabaseManager { return } } - + + print("Add operation: fetchChangesInDatabase") database.add(changesOperation) } @@ -90,7 +91,8 @@ final class PrivateDatabaseManager: DatabaseManager { return } } - + + print("Add operation: createCustomZonesIfAllowed") database.add(modifyOp) } @@ -110,6 +112,7 @@ final class PrivateDatabaseManager: DatabaseManager { self.subscriptionIsLocallyCached = true } createOp.qualityOfService = .utility + print("Add operation: createDatabaseSubscriptionIfHaveNot") database.add(createOp) #endif } @@ -133,6 +136,14 @@ final class PrivateDatabaseManager: DatabaseManager { } } } + + func unregisterLocalDatabase() { + self.syncObjects.forEach { object in + DispatchQueue.main.async { + object.unregisterLocalDatabase() + } + } + } private func fetchChangesInZones(_ callback: ((Error?) -> Void)? = nil) { let changesOp = CKFetchRecordZoneChangesOperation(recordZoneIDs: zoneIds, optionsByRecordZoneID: zoneIdOptions) @@ -190,7 +201,8 @@ final class PrivateDatabaseManager: DatabaseManager { } callback?(error) } - + + print("Add operation: fetchChangesInZones") database.add(changesOp) } } diff --git a/IceCream/Classes/PublicDatabaseManager.swift b/IceCream/Classes/PublicDatabaseManager.swift index ff901701..c6de6223 100644 --- a/IceCream/Classes/PublicDatabaseManager.swift +++ b/IceCream/Classes/PublicDatabaseManager.swift @@ -62,6 +62,14 @@ final class PublicDatabaseManager: DatabaseManager { } } } + + func unregisterLocalDatabase() { + self.syncObjects.forEach { object in + DispatchQueue.main.async { + object.unregisterLocalDatabase() + } + } + } // MARK: - Private Methods private func excuteQueryOperation(queryOperation: CKQueryOperation,on syncObject: Syncable, callback: ((Error?) -> Void)? = nil) { @@ -89,7 +97,8 @@ final class PublicDatabaseManager: DatabaseManager { break } } - + + print("Add operation: excuteQueryOperation") database.add(queryOperation) } @@ -108,6 +117,7 @@ final class PublicDatabaseManager: DatabaseManager { } createOp.qualityOfService = .utility + print("Add operation: createSubscriptionInPublicDatabase") database.add(createOp) #endif } diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 3a22e357..4537a574 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -86,6 +86,14 @@ extension SyncEngine { let recordsIDsToDelete: [CKRecord.ID] = objects.filter { $0.isDeleted }.map { $0.recordID } databaseManager.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordsIDsToDelete, completion: completionHandler) } + + public func registerLocalDatabase() { + databaseManager.registerLocalDatabase() + } + + public func unregisterLocalDatabase() { + databaseManager.unregisterLocalDatabase() + } } public enum Notifications: String, NotificationName { diff --git a/IceCream/Classes/SyncObject.swift b/IceCream/Classes/SyncObject.swift index 8a74b6de..1fe31a13 100644 --- a/IceCream/Classes/SyncObject.swift +++ b/IceCream/Classes/SyncObject.swift @@ -150,6 +150,12 @@ extension SyncObject: Syncable { }) } } + + public func unregisterLocalDatabase() { + BackgroundWorker.shared.start { + self.notificationToken = nil + } + } public func resolvePendingRelationships() { pendingUTypeRelationshipsWorker.resolvePendingListElements() diff --git a/IceCream/Classes/Syncable.swift b/IceCream/Classes/Syncable.swift index 4b1568b0..b1457c18 100644 --- a/IceCream/Classes/Syncable.swift +++ b/IceCream/Classes/Syncable.swift @@ -23,6 +23,7 @@ public protocol Syncable: class { /// Realm Database related func registerLocalDatabase() + func unregisterLocalDatabase() func cleanUp() func add(record: CKRecord) func delete(recordID: CKRecord.ID) From e82c6f2b42bfd9ec0956c0bc9888a65acc24cf89 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Wed, 8 Sep 2021 17:48:40 +0800 Subject: [PATCH 13/25] =?UTF-8?q?=E4=B8=BA=E4=BA=86=E8=83=BD=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=EF=BC=8C=E4=B8=8D=20Resume=20Long=20Lived=20Operaiton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 536630fe..fe6806c1 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -65,7 +65,7 @@ extension DatabaseManager { } if #available(iOS 15, *) { print("Add operation: fetchLongLivedOperation") - self.database.add(modifyOp) + // self.database.add(modifyOp) } else { self.container.add(modifyOp) } From 9030be671a140b65db1f86605e26c4041affe1d2 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 14:50:50 +0800 Subject: [PATCH 14/25] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20SyncEngine?= =?UTF-8?q?=20=E6=97=B6=EF=BC=8C=E6=94=AF=E6=8C=81=E4=B8=8D=E5=BC=80?= =?UTF-8?q?=E5=A7=8B=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example/IceCream_Example/AppDelegate.swift | 15 +++++--- .../Classes/DatabaseManagerProtocol.swift | 6 +++ IceCream/Classes/PrivateDatabaseManager.swift | 12 ++++++ IceCream/Classes/PublicDatabaseManager.swift | 12 ++++++ IceCream/Classes/SyncEngine.swift | 38 ++++++++++++------- 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/Example/IceCream_Example/AppDelegate.swift b/Example/IceCream_Example/AppDelegate.swift index 937fdd27..24667a63 100644 --- a/Example/IceCream_Example/AppDelegate.swift +++ b/Example/IceCream_Example/AppDelegate.swift @@ -19,11 +19,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - syncEngine = SyncEngine(objects: [ - SyncObject(type: Dog.self), - SyncObject(type: Cat.self), - SyncObject(type: Person.self, uListElementType: Cat.self) - ]) + syncEngine = SyncEngine( + objects: [ + SyncObject(type: Dog.self), + SyncObject(type: Cat.self), + SyncObject(type: Person.self, uListElementType: Cat.self) + ], + callback: { syncEngine in + syncEngine.pull(completionHandler: nil) + syncEngine.startObservingLocalAndRemoteChanges() + }) /// If you wanna test public Database, comment the above syncEngine code and uncomment the following one /// Besides, uncomment Line 26 to 28 in Person.swift file diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index fe6806c1..42ae1a55 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -34,7 +34,9 @@ protocol DatabaseManager: class { func createCustomZonesIfAllowed() func startObservingRemoteChanges() + func stopObservingRemoteChanges() func startObservingTermination() + func stopObservingTermination() func createDatabaseSubscriptionIfHaveNot() func registerLocalDatabase() func unregisterLocalDatabase() @@ -83,6 +85,10 @@ extension DatabaseManager { } }) } + + func stopObservingRemoteChanges() { + NotificationCenter.default.removeObserver(self, name: Notifications.cloudKitDataDidChangeRemotely.name, object: nil) + } /// Sync local data to CloudKit /// For more about the savePolicy: https://developer.apple.com/documentation/cloudkit/ckrecordsavepolicy diff --git a/IceCream/Classes/PrivateDatabaseManager.swift b/IceCream/Classes/PrivateDatabaseManager.swift index e8fb5e3a..adb69bd8 100644 --- a/IceCream/Classes/PrivateDatabaseManager.swift +++ b/IceCream/Classes/PrivateDatabaseManager.swift @@ -128,6 +128,18 @@ final class PrivateDatabaseManager: DatabaseManager { #endif } + + func stopObservingTermination() { + #if os(iOS) || os(tvOS) + + NotificationCenter.default.removeObserver(self, name: UIApplication.willTerminateNotification, object: nil) + + #elseif os(macOS) + + NotificationCenter.default.removeObserver(self, name: NSApplication.willTerminateNotification, object: nil) + + #endif + } func registerLocalDatabase() { self.syncObjects.forEach { object in diff --git a/IceCream/Classes/PublicDatabaseManager.swift b/IceCream/Classes/PublicDatabaseManager.swift index c6de6223..fbcee2c1 100644 --- a/IceCream/Classes/PublicDatabaseManager.swift +++ b/IceCream/Classes/PublicDatabaseManager.swift @@ -54,6 +54,18 @@ final class PublicDatabaseManager: DatabaseManager { #endif } + + func stopObservingTermination() { + #if os(iOS) || os(tvOS) + + NotificationCenter.default.removeObserver(self, name: UIApplication.willTerminateNotification, object: nil) + + #elseif os(macOS) + + NotificationCenter.default.removeObserver(self, name: NSApplication.willTerminateNotification, object: nil) + + #endif + } func registerLocalDatabase() { syncObjects.forEach { object in diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 4537a574..8249f3a5 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -17,7 +17,7 @@ public final class SyncEngine { private let databaseManager: DatabaseManager - public convenience init(objects: [Syncable], databaseScope: CKDatabase.Scope = .private, container: CKContainer = .default(), callback: ((Error?) -> Void)? = nil) { + public convenience init(objects: [Syncable], databaseScope: CKDatabase.Scope = .private, container: CKContainer = .default(), callback: @escaping (SyncEngine) -> Void) { switch databaseScope { case .private: let privateDatabaseManager = PrivateDatabaseManager(objects: objects, container: container) @@ -30,31 +30,33 @@ public final class SyncEngine { } } - private init(databaseManager: DatabaseManager, callback: ((Error?) -> Void)?) { + private init(databaseManager: DatabaseManager, callback: @escaping (SyncEngine) -> Void) { self.databaseManager = databaseManager setup(callback) } - private func setup(_ callback: ((Error?) -> Void)?) { + private func setup(_ callback: @escaping (SyncEngine) -> Void) { databaseManager.prepare() databaseManager.container.accountStatus { [weak self] (status, error) in guard let self = self else { return } switch status { case .available: - self.databaseManager.registerLocalDatabase() + // self.databaseManager.registerLocalDatabase() self.databaseManager.createCustomZonesIfAllowed() - self.databaseManager.fetchChangesInDatabase(callback) + // self.databaseManager.fetchChangesInDatabase(callback) self.databaseManager.resumeLongLivedOperationIfPossible() - self.databaseManager.startObservingRemoteChanges() - self.databaseManager.startObservingTermination() + // self.databaseManager.startObservingRemoteChanges() + // self.databaseManager.startObservingTermination() self.databaseManager.createDatabaseSubscriptionIfHaveNot() + callback(self) case .noAccount, .restricted: guard self.databaseManager is PublicDatabaseManager else { break } - self.databaseManager.fetchChangesInDatabase(callback) + // self.databaseManager.fetchChangesInDatabase(callback) self.databaseManager.resumeLongLivedOperationIfPossible() - self.databaseManager.startObservingRemoteChanges() - self.databaseManager.startObservingTermination() + // self.databaseManager.startObservingRemoteChanges() + // self.databaseManager.startObservingTermination() self.databaseManager.createDatabaseSubscriptionIfHaveNot() + callback(self) case .couldNotDetermine: break @unknown default: @@ -87,12 +89,20 @@ extension SyncEngine { databaseManager.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordsIDsToDelete, completion: completionHandler) } - public func registerLocalDatabase() { - databaseManager.registerLocalDatabase() + public func startObservingLocalAndRemoteChanges() { + if self.databaseManager is PrivateDatabaseManager { + databaseManager.registerLocalDatabase() + } + databaseManager.startObservingRemoteChanges() + databaseManager.startObservingTermination() } - public func unregisterLocalDatabase() { - databaseManager.unregisterLocalDatabase() + public func stopObservingLocalAndRemoteChanges() { + if self.databaseManager is PrivateDatabaseManager { + databaseManager.unregisterLocalDatabase() + } + databaseManager.stopObservingRemoteChanges() + databaseManager.stopObservingTermination() } } From 7c0020fd4baf09d21a717a39ef4f14a08640238d Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 15:00:17 +0800 Subject: [PATCH 15/25] =?UTF-8?q?=E9=81=BF=E5=85=8D=E4=B8=BA=20notificatio?= =?UTF-8?q?n=20=E5=A2=9E=E5=8A=A0=E5=A4=9A=E4=B8=AA=20obsever?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/SyncEngine.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 8249f3a5..4e1ebf59 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -90,6 +90,8 @@ extension SyncEngine { } public func startObservingLocalAndRemoteChanges() { + stopObservingLocalAndRemoteChanges() // Avoid add observer for multiple times. + if self.databaseManager is PrivateDatabaseManager { databaseManager.registerLocalDatabase() } From 4ef748bea18d0fbd32d70ca8da1867f46566eea5 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 15:35:27 +0800 Subject: [PATCH 16/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20debug=20=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/DatabaseManagerProtocol.swift | 6 ++--- IceCream/Classes/PrivateDatabaseManager.swift | 22 +++++++++++++++---- IceCream/Classes/PublicDatabaseManager.swift | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 42ae1a55..770bbd48 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -63,10 +63,10 @@ extension DatabaseManager { guard let self = self, error == nil else { return } if let modifyOp = ope as? CKModifyRecordsOperation { modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in - print("Resume modify records success!") + print("🍦 Resume modify records success!") } if #available(iOS 15, *) { - print("Add operation: fetchLongLivedOperation") + print("🍦 Add operation: fetchLongLivedOperation") // self.database.add(modifyOp) } else { self.container.add(modifyOp) @@ -140,7 +140,7 @@ extension DatabaseManager { } } - print("Add operation: syncRecordsToCloudKit") + print("🍦 Add operation: syncRecordsToCloudKit") database.add(modifyOpe) } diff --git a/IceCream/Classes/PrivateDatabaseManager.swift b/IceCream/Classes/PrivateDatabaseManager.swift index adb69bd8..14c0d155 100644 --- a/IceCream/Classes/PrivateDatabaseManager.swift +++ b/IceCream/Classes/PrivateDatabaseManager.swift @@ -31,6 +31,7 @@ final class PrivateDatabaseManager: DatabaseManager { /// Only update the changeToken when fetch process completes changesOperation.changeTokenUpdatedBlock = { [weak self] newToken in + print("🍦 Database change token updated") self?.databaseChangeToken = newToken } @@ -40,28 +41,33 @@ final class PrivateDatabaseManager: DatabaseManager { guard let self = self else { return } switch ErrorHandler.shared.resultType(with: error) { case .success: + print("🍦 Database fetch completed, token updated") self.databaseChangeToken = newToken // Fetch the changes in zone level self.fetchChangesInZones(callback) case .retry(let timeToWait, _): + print("🍦 Database fetch needs retry") ErrorHandler.shared.retryOperationIfPossible(retryAfter: timeToWait, block: { self.fetchChangesInDatabase(callback) }) case .recoverableError(let reason, _): switch reason { case .changeTokenExpired: + print("🍦 Database fetch token expired") /// The previousServerChangeToken value is too old and the client must re-sync from scratch self.databaseChangeToken = nil self.fetchChangesInDatabase(callback) default: + print("🍦 Database fetch unhandled recoverable error: \(reason)") return } default: + print("🍦 Database unhandled completion: \(error.debugDescription)") return } } - print("Add operation: fetchChangesInDatabase") + print("🍦 Add operation: fetchChangesInDatabase") database.add(changesOperation) } @@ -92,7 +98,7 @@ final class PrivateDatabaseManager: DatabaseManager { } } - print("Add operation: createCustomZonesIfAllowed") + print("🍦 Add operation: createCustomZonesIfAllowed") database.add(modifyOp) } @@ -112,7 +118,7 @@ final class PrivateDatabaseManager: DatabaseManager { self.subscriptionIsLocallyCached = true } createOp.qualityOfService = .utility - print("Add operation: createDatabaseSubscriptionIfHaveNot") + print("🍦 Add operation: createDatabaseSubscriptionIfHaveNot") database.add(createOp) #endif } @@ -162,6 +168,7 @@ final class PrivateDatabaseManager: DatabaseManager { changesOp.fetchAllChanges = true changesOp.recordZoneChangeTokensUpdatedBlock = { [weak self] zoneId, token, _ in + print("🍦 Record zone \(zoneId) change token updated") guard let self = self else { return } guard let syncObject = self.syncObjects.first(where: { $0.zoneID == zoneId }) else { return } syncObject.zoneChangesToken = token @@ -170,12 +177,14 @@ final class PrivateDatabaseManager: DatabaseManager { changesOp.recordChangedBlock = { [weak self] record in /// The Cloud will return the modified record since the last zoneChangesToken, we need to do local cache here. /// Handle the record: + print("🍦 Record zone \(record.recordID) changed") guard let self = self else { return } guard let syncObject = self.syncObjects.first(where: { $0.recordType == record.recordType }) else { return } syncObject.add(record: record) } changesOp.recordWithIDWasDeletedBlock = { [weak self] recordId, _ in + print("🍦 Record \(recordId) deleted") guard let self = self else { return } guard let syncObject = self.syncObjects.first(where: { $0.zoneID == recordId.zoneID }) else { return } syncObject.delete(recordID: recordId) @@ -185,23 +194,28 @@ final class PrivateDatabaseManager: DatabaseManager { guard let self = self else { return } switch ErrorHandler.shared.resultType(with: error) { case .success: + print("🍦 Record zone \(zoneId) fetch completed, token updated") guard let syncObject = self.syncObjects.first(where: { $0.zoneID == zoneId }) else { return } syncObject.zoneChangesToken = token case .retry(let timeToWait, _): + print("🍦 Record zone \(zoneId) fetch needs retry") ErrorHandler.shared.retryOperationIfPossible(retryAfter: timeToWait, block: { self.fetchChangesInZones(callback) }) case .recoverableError(let reason, _): switch reason { case .changeTokenExpired: + print("🍦 Record zone \(zoneId) token expired") /// The previousServerChangeToken value is too old and the client must re-sync from scratch guard let syncObject = self.syncObjects.first(where: { $0.zoneID == zoneId }) else { return } syncObject.zoneChangesToken = nil self.fetchChangesInZones(callback) default: + print("🍦 Record zone \(zoneId) unhandled recoverable error: \(reason)") return } default: + print("🍦 Record zone \(zoneId) unhandled completion: \(error.debugDescription)") return } } @@ -214,7 +228,7 @@ final class PrivateDatabaseManager: DatabaseManager { callback?(error) } - print("Add operation: fetchChangesInZones") + print("🍦 Add operation: fetchChangesInZones") database.add(changesOp) } } diff --git a/IceCream/Classes/PublicDatabaseManager.swift b/IceCream/Classes/PublicDatabaseManager.swift index fbcee2c1..171337bc 100644 --- a/IceCream/Classes/PublicDatabaseManager.swift +++ b/IceCream/Classes/PublicDatabaseManager.swift @@ -110,7 +110,7 @@ final class PublicDatabaseManager: DatabaseManager { } } - print("Add operation: excuteQueryOperation") + print("🍦 Add operation: excuteQueryOperation") database.add(queryOperation) } @@ -129,7 +129,7 @@ final class PublicDatabaseManager: DatabaseManager { } createOp.qualityOfService = .utility - print("Add operation: createSubscriptionInPublicDatabase") + print("🍦 Add operation: createSubscriptionInPublicDatabase") database.add(createOp) #endif } From 0e14130201de5313ab8e1160b5f9a02389d27cbf Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 15:37:48 +0800 Subject: [PATCH 17/25] =?UTF-8?q?Push=20=E6=95=B0=E6=8D=AE=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 770bbd48..c52ad8a7 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -121,14 +121,17 @@ extension DatabaseManager { switch ErrorHandler.shared.resultType(with: error) { case .success: + print("🍦 Modify record completed") DispatchQueue.main.async { completion?(nil) } case .retry(let timeToWait, _): + print("🍦 Modify record needs retry") ErrorHandler.shared.retryOperationIfPossible(retryAfter: timeToWait) { self.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordIDsToDelete, completion: completion) } case .chunk: + print("🍦 Modify record needs chunk") /// CloudKit says maximum number of items in a single request is 400. /// So I think 300 should be fine by them. let chunkedRecords = recordsToStore.chunkItUp(by: 300) @@ -136,6 +139,7 @@ extension DatabaseManager { self.syncRecordsToCloudKit(recordsToStore: chunk, recordIDsToDelete: recordIDsToDelete, completion: completion) } default: + print("🍦 Modify record unhandled completion: \(error.debugDescription)") return } } From b7d11bc77ab28eb3f05a464951d0b530dc7a59be Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 16:13:37 +0800 Subject: [PATCH 18/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E7=9A=84=E8=AE=B0=E5=BD=95=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index c52ad8a7..dbea8740 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -144,7 +144,7 @@ extension DatabaseManager { } } - print("🍦 Add operation: syncRecordsToCloudKit") + print("🍦 Add operation: syncRecordsToCloudKit store \(recordsToStore.count) \(recordsToStore.first?.recordType ?? "unknown") objects, delete \(recordIDsToDelete.count) objects") database.add(modifyOpe) } From 8c444b5f5b911bfb0233f27e1635811b2c1e8f21 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Thu, 9 Sep 2021 19:14:18 +0800 Subject: [PATCH 19/25] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20add=20long=20lived?= =?UTF-8?q?=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index dbea8740..0d1cd43c 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -67,7 +67,7 @@ extension DatabaseManager { } if #available(iOS 15, *) { print("🍦 Add operation: fetchLongLivedOperation") - // self.database.add(modifyOp) + self.database.add(modifyOp) } else { self.container.add(modifyOp) } From 4cdb79f641810b77aeff672c910903b2fc8b0821 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Fri, 8 Apr 2022 16:16:21 +0800 Subject: [PATCH 20/25] Support pause sync object --- IceCream/Classes/SyncEngine.swift | 34 ++++++++++++++++----------- IceCream/Classes/SyncObject.swift | 38 ++++++++++++++++++++++++------- IceCream/Classes/Syncable.swift | 2 ++ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index 4e1ebf59..e851b729 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -89,22 +89,30 @@ extension SyncEngine { databaseManager.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordsIDsToDelete, completion: completionHandler) } - public func startObservingLocalAndRemoteChanges() { - stopObservingLocalAndRemoteChanges() // Avoid add observer for multiple times. +// public func startObservingLocalAndRemoteChanges() { +// stopObservingLocalAndRemoteChanges() // Avoid add observer for multiple times. +// +// if self.databaseManager is PrivateDatabaseManager { +// databaseManager.registerLocalDatabase() +// } +// databaseManager.startObservingRemoteChanges() +// databaseManager.startObservingTermination() +// } +// +// public func stopObservingLocalAndRemoteChanges() { +// if self.databaseManager is PrivateDatabaseManager { +// databaseManager.unregisterLocalDatabase() +// } +// databaseManager.stopObservingRemoteChanges() +// databaseManager.stopObservingTermination() +// } - if self.databaseManager is PrivateDatabaseManager { - databaseManager.registerLocalDatabase() - } - databaseManager.startObservingRemoteChanges() - databaseManager.startObservingTermination() + public func partiallyPauseSync() { + databaseManager.syncObjects.forEach { $0.pause() } } - public func stopObservingLocalAndRemoteChanges() { - if self.databaseManager is PrivateDatabaseManager { - databaseManager.unregisterLocalDatabase() - } - databaseManager.stopObservingRemoteChanges() - databaseManager.stopObservingTermination() + public func resumeSync() { + databaseManager.syncObjects.forEach { $0.resume() } } } diff --git a/IceCream/Classes/SyncObject.swift b/IceCream/Classes/SyncObject.swift index 1fe31a13..7c17c082 100644 --- a/IceCream/Classes/SyncObject.swift +++ b/IceCream/Classes/SyncObject.swift @@ -28,15 +28,21 @@ public final class SyncObject where T: Object & CKRecordConvertible private let pendingUTypeRelationshipsWorker = PendingRelationshipsWorker() private let pendingVTypeRelationshipsWorker = PendingRelationshipsWorker() private let pendingWTypeRelationshipsWorker = PendingRelationshipsWorker() - + + public var canPauseSync: Bool + private var isPaused: Bool + public init( realmConfiguration: Realm.Configuration = .defaultConfiguration, type: T.Type, + canPauseSync: Bool = true, uListElementType: U.Type? = nil, vListElementType: V.Type? = nil, wListElementType: W.Type? = nil ) { self.realmConfiguration = realmConfiguration + self.canPauseSync = canPauseSync + self.isPaused = false } } @@ -127,14 +133,30 @@ extension SyncObject: Syncable { } } } - + + public func pause() { + guard self.canPauseSync else { + return + } + + self.isPaused = true + } + + public func resume() { + guard self.canPauseSync else { + return + } + + self.isPaused = false + } + /// When you commit a write transaction to a Realm, all other instances of that Realm will be notified, and be updated automatically. /// For more: https://realm.io/docs/swift/latest/#writes public func registerLocalDatabase() { BackgroundWorker.shared.start { let realm = try! Realm(configuration: self.realmConfiguration) self.notificationToken = realm.objects(T.self).observe({ [weak self](changes) in - guard let self = self else { return } + guard let self = self, !self.isPaused else { return } switch changes { case .initial(_): break @@ -151,11 +173,11 @@ extension SyncObject: Syncable { } } - public func unregisterLocalDatabase() { - BackgroundWorker.shared.start { - self.notificationToken = nil - } - } +// public func unregisterLocalDatabase() { +// BackgroundWorker.shared.start { +// self.notificationToken = nil +// } +// } public func resolvePendingRelationships() { pendingUTypeRelationshipsWorker.resolvePendingListElements() diff --git a/IceCream/Classes/Syncable.swift b/IceCream/Classes/Syncable.swift index b1457c18..818a54e6 100644 --- a/IceCream/Classes/Syncable.swift +++ b/IceCream/Classes/Syncable.swift @@ -32,6 +32,8 @@ public protocol Syncable: class { /// CloudKit related func pushLocalObjectsToCloudKit() + func pause() + func resume() /// Callback var pipeToEngine: ((_ recordsToStore: [CKRecord], _ recordIDsToDelete: [CKRecord.ID]) -> ())? { get set } From 80ffa3eab1722d220265536848581b8792042182 Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Fri, 8 Apr 2022 16:22:57 +0800 Subject: [PATCH 21/25] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8E=89=20unregisterL?= =?UTF-8?q?ocalDatabase=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/DatabaseManagerProtocol.swift | 2 +- IceCream/Classes/PrivateDatabaseManager.swift | 14 +++++++------- IceCream/Classes/PublicDatabaseManager.swift | 14 +++++++------- IceCream/Classes/Syncable.swift | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 0d1cd43c..e55dfd81 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -39,7 +39,7 @@ protocol DatabaseManager: class { func stopObservingTermination() func createDatabaseSubscriptionIfHaveNot() func registerLocalDatabase() - func unregisterLocalDatabase() +// func unregisterLocalDatabase() func cleanUp() } diff --git a/IceCream/Classes/PrivateDatabaseManager.swift b/IceCream/Classes/PrivateDatabaseManager.swift index 14c0d155..74092969 100644 --- a/IceCream/Classes/PrivateDatabaseManager.swift +++ b/IceCream/Classes/PrivateDatabaseManager.swift @@ -155,13 +155,13 @@ final class PrivateDatabaseManager: DatabaseManager { } } - func unregisterLocalDatabase() { - self.syncObjects.forEach { object in - DispatchQueue.main.async { - object.unregisterLocalDatabase() - } - } - } +// func unregisterLocalDatabase() { +// self.syncObjects.forEach { object in +// DispatchQueue.main.async { +// object.unregisterLocalDatabase() +// } +// } +// } private func fetchChangesInZones(_ callback: ((Error?) -> Void)? = nil) { let changesOp = CKFetchRecordZoneChangesOperation(recordZoneIDs: zoneIds, optionsByRecordZoneID: zoneIdOptions) diff --git a/IceCream/Classes/PublicDatabaseManager.swift b/IceCream/Classes/PublicDatabaseManager.swift index 171337bc..6719a05c 100644 --- a/IceCream/Classes/PublicDatabaseManager.swift +++ b/IceCream/Classes/PublicDatabaseManager.swift @@ -75,13 +75,13 @@ final class PublicDatabaseManager: DatabaseManager { } } - func unregisterLocalDatabase() { - self.syncObjects.forEach { object in - DispatchQueue.main.async { - object.unregisterLocalDatabase() - } - } - } +// func unregisterLocalDatabase() { +// self.syncObjects.forEach { object in +// DispatchQueue.main.async { +// object.unregisterLocalDatabase() +// } +// } +// } // MARK: - Private Methods private func excuteQueryOperation(queryOperation: CKQueryOperation,on syncObject: Syncable, callback: ((Error?) -> Void)? = nil) { diff --git a/IceCream/Classes/Syncable.swift b/IceCream/Classes/Syncable.swift index 818a54e6..69bd26ec 100644 --- a/IceCream/Classes/Syncable.swift +++ b/IceCream/Classes/Syncable.swift @@ -23,7 +23,7 @@ public protocol Syncable: class { /// Realm Database related func registerLocalDatabase() - func unregisterLocalDatabase() +// func unregisterLocalDatabase() func cleanUp() func add(record: CKRecord) func delete(recordID: CKRecord.ID) From 10f783309587c6b8792670a48c0bc44161ec5cce Mon Sep 17 00:00:00 2001 From: Alen Liang Date: Fri, 8 Apr 2022 16:34:50 +0800 Subject: [PATCH 22/25] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/DatabaseManagerProtocol.swift | 2 +- IceCream/Classes/PrivateDatabaseManager.swift | 14 ++++---- IceCream/Classes/PublicDatabaseManager.swift | 14 ++++---- IceCream/Classes/SyncEngine.swift | 34 +++++++++---------- IceCream/Classes/SyncObject.swift | 10 +++--- IceCream/Classes/Syncable.swift | 2 +- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index e55dfd81..0d1cd43c 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -39,7 +39,7 @@ protocol DatabaseManager: class { func stopObservingTermination() func createDatabaseSubscriptionIfHaveNot() func registerLocalDatabase() -// func unregisterLocalDatabase() + func unregisterLocalDatabase() func cleanUp() } diff --git a/IceCream/Classes/PrivateDatabaseManager.swift b/IceCream/Classes/PrivateDatabaseManager.swift index 74092969..14c0d155 100644 --- a/IceCream/Classes/PrivateDatabaseManager.swift +++ b/IceCream/Classes/PrivateDatabaseManager.swift @@ -155,13 +155,13 @@ final class PrivateDatabaseManager: DatabaseManager { } } -// func unregisterLocalDatabase() { -// self.syncObjects.forEach { object in -// DispatchQueue.main.async { -// object.unregisterLocalDatabase() -// } -// } -// } + func unregisterLocalDatabase() { + self.syncObjects.forEach { object in + DispatchQueue.main.async { + object.unregisterLocalDatabase() + } + } + } private func fetchChangesInZones(_ callback: ((Error?) -> Void)? = nil) { let changesOp = CKFetchRecordZoneChangesOperation(recordZoneIDs: zoneIds, optionsByRecordZoneID: zoneIdOptions) diff --git a/IceCream/Classes/PublicDatabaseManager.swift b/IceCream/Classes/PublicDatabaseManager.swift index 6719a05c..171337bc 100644 --- a/IceCream/Classes/PublicDatabaseManager.swift +++ b/IceCream/Classes/PublicDatabaseManager.swift @@ -75,13 +75,13 @@ final class PublicDatabaseManager: DatabaseManager { } } -// func unregisterLocalDatabase() { -// self.syncObjects.forEach { object in -// DispatchQueue.main.async { -// object.unregisterLocalDatabase() -// } -// } -// } + func unregisterLocalDatabase() { + self.syncObjects.forEach { object in + DispatchQueue.main.async { + object.unregisterLocalDatabase() + } + } + } // MARK: - Private Methods private func excuteQueryOperation(queryOperation: CKQueryOperation,on syncObject: Syncable, callback: ((Error?) -> Void)? = nil) { diff --git a/IceCream/Classes/SyncEngine.swift b/IceCream/Classes/SyncEngine.swift index e851b729..ab88331e 100644 --- a/IceCream/Classes/SyncEngine.swift +++ b/IceCream/Classes/SyncEngine.swift @@ -89,23 +89,23 @@ extension SyncEngine { databaseManager.syncRecordsToCloudKit(recordsToStore: recordsToStore, recordIDsToDelete: recordsIDsToDelete, completion: completionHandler) } -// public func startObservingLocalAndRemoteChanges() { -// stopObservingLocalAndRemoteChanges() // Avoid add observer for multiple times. -// -// if self.databaseManager is PrivateDatabaseManager { -// databaseManager.registerLocalDatabase() -// } -// databaseManager.startObservingRemoteChanges() -// databaseManager.startObservingTermination() -// } -// -// public func stopObservingLocalAndRemoteChanges() { -// if self.databaseManager is PrivateDatabaseManager { -// databaseManager.unregisterLocalDatabase() -// } -// databaseManager.stopObservingRemoteChanges() -// databaseManager.stopObservingTermination() -// } + public func startObservingLocalAndRemoteChanges() { + stopObservingLocalAndRemoteChanges() // Avoid add observer for multiple times. + + if self.databaseManager is PrivateDatabaseManager { + databaseManager.registerLocalDatabase() + } + databaseManager.startObservingRemoteChanges() + databaseManager.startObservingTermination() + } + + public func stopObservingLocalAndRemoteChanges() { + if self.databaseManager is PrivateDatabaseManager { + databaseManager.unregisterLocalDatabase() + } + databaseManager.stopObservingRemoteChanges() + databaseManager.stopObservingTermination() + } public func partiallyPauseSync() { databaseManager.syncObjects.forEach { $0.pause() } diff --git a/IceCream/Classes/SyncObject.swift b/IceCream/Classes/SyncObject.swift index 7c17c082..784c2ca9 100644 --- a/IceCream/Classes/SyncObject.swift +++ b/IceCream/Classes/SyncObject.swift @@ -173,11 +173,11 @@ extension SyncObject: Syncable { } } -// public func unregisterLocalDatabase() { -// BackgroundWorker.shared.start { -// self.notificationToken = nil -// } -// } + public func unregisterLocalDatabase() { + BackgroundWorker.shared.start { + self.notificationToken = nil + } + } public func resolvePendingRelationships() { pendingUTypeRelationshipsWorker.resolvePendingListElements() diff --git a/IceCream/Classes/Syncable.swift b/IceCream/Classes/Syncable.swift index 69bd26ec..818a54e6 100644 --- a/IceCream/Classes/Syncable.swift +++ b/IceCream/Classes/Syncable.swift @@ -23,7 +23,7 @@ public protocol Syncable: class { /// Realm Database related func registerLocalDatabase() -// func unregisterLocalDatabase() + func unregisterLocalDatabase() func cleanUp() func add(record: CKRecord) func delete(recordID: CKRecord.ID) From 8939fc966f4bb9138ebd536b5585921a61d08334 Mon Sep 17 00:00:00 2001 From: Liu Yi <61upup@gmail.com> Date: Thu, 28 Apr 2022 15:21:03 +0800 Subject: [PATCH 23/25] Cancel fetched long-lived operation if it is not finished --- IceCream/Classes/DatabaseManagerProtocol.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/IceCream/Classes/DatabaseManagerProtocol.swift b/IceCream/Classes/DatabaseManagerProtocol.swift index 0d1cd43c..f53674dd 100644 --- a/IceCream/Classes/DatabaseManagerProtocol.swift +++ b/IceCream/Classes/DatabaseManagerProtocol.swift @@ -62,11 +62,23 @@ extension DatabaseManager { self.container.fetchLongLivedOperation(withID: id, completionHandler: { [weak self](ope, error) in guard let self = self, error == nil else { return } if let modifyOp = ope as? CKModifyRecordsOperation { - modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in - print("🍦 Resume modify records success!") + if #available(iOS 15, *) { + modifyOp.modifyRecordsResultBlock = { result in + print("🍦 modify result: \(result)") + } + } else { + modifyOp.modifyRecordsCompletionBlock = { (_,_,_) in + print("🍦 Resume modify records success!") + } } + + if let isFinished = ope?.isFinished, !isFinished { + print("🍦 Cancel operation") + ope?.cancel() + } + if #available(iOS 15, *) { - print("🍦 Add operation: fetchLongLivedOperation") + print("🍦 Add fetched long-lived operation") self.database.add(modifyOp) } else { self.container.add(modifyOp) From 72c26f878cc9240fd4c3b4f04b24f7551fc26ab4 Mon Sep 17 00:00:00 2001 From: Liu Yi <61upup@gmail.com> Date: Sat, 30 Apr 2022 11:05:10 +0800 Subject: [PATCH 24/25] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=9B=B4=E6=96=B0=20lo?= =?UTF-8?q?g=20=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IceCream/Classes/Manifest.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/IceCream/Classes/Manifest.swift b/IceCream/Classes/Manifest.swift index 4d00644d..d9fb9022 100644 --- a/IceCream/Classes/Manifest.swift +++ b/IceCream/Classes/Manifest.swift @@ -17,19 +17,15 @@ public class IceCream { /// If you don't want to see them in your console, just set `enableLogging` property to false. /// The default value is true. public var enableLogging: Bool = true - + public var print: ((Any...) -> Void)? = nil } /// If you want to know more, /// this post would help: https://medium.com/@maxcampolo/swift-conditional-logging-compiler-flags-54692dc86c5f internal func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { if (IceCream.shared.enableLogging) { - #if DEBUG - var i = items.startIndex - repeat { - Swift.print(items[i], separator: separator, terminator: i == (items.endIndex - 1) ? terminator : separator) - i += 1 - } while i < items.endIndex - #endif + if let print = IceCream.shared.print { + print(items) + } } } From 045af2aae256d3109da4ec4d8f3253aa9cde8107 Mon Sep 17 00:00:00 2001 From: Liu Yi <61upup@gmail.com> Date: Mon, 2 May 2022 18:48:30 +0800 Subject: [PATCH 25/25] =?UTF-8?q?Revert=20"=E4=B8=B4=E6=97=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20log=20=E6=96=B9=E5=BC=8F"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 72c26f878cc9240fd4c3b4f04b24f7551fc26ab4. --- IceCream/Classes/Manifest.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/IceCream/Classes/Manifest.swift b/IceCream/Classes/Manifest.swift index d9fb9022..4d00644d 100644 --- a/IceCream/Classes/Manifest.swift +++ b/IceCream/Classes/Manifest.swift @@ -17,15 +17,19 @@ public class IceCream { /// If you don't want to see them in your console, just set `enableLogging` property to false. /// The default value is true. public var enableLogging: Bool = true - public var print: ((Any...) -> Void)? = nil + } /// If you want to know more, /// this post would help: https://medium.com/@maxcampolo/swift-conditional-logging-compiler-flags-54692dc86c5f internal func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { if (IceCream.shared.enableLogging) { - if let print = IceCream.shared.print { - print(items) - } + #if DEBUG + var i = items.startIndex + repeat { + Swift.print(items[i], separator: separator, terminator: i == (items.endIndex - 1) ? terminator : separator) + i += 1 + } while i < items.endIndex + #endif } }