Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b9db980
container.add -> database.add
aalenliang Jul 29, 2021
f1f98cc
增加 push 部分 object 方法
aalenliang Jul 29, 2021
fd447b2
pushAll 选择类型
aalenliang Jul 29, 2021
03508c8
去掉判断
aalenliang Jul 29, 2021
057486e
还是不修改 pushAll 了
aalenliang Jul 29, 2021
923bed3
database -> container // https://developer.apple.com/documentation/cl…
aalenliang Jul 29, 2021
21cad10
iOS 15 不再处理 long lived operation
aalenliang Jul 29, 2021
148a908
恢复这个写法
aalenliang Jul 29, 2021
4a4dbe8
初始化时增加 pull 的回调
aalenliang Aug 26, 2021
24a73df
iOS 15 与其他版本区分处理 longLiveOperation
aalenliang Sep 2, 2021
f9c5545
去掉 iOS 15 以前没有的 case
aalenliang Sep 2, 2021
b2ae852
提供暂停监听 changes 通知方法
aalenliang Sep 7, 2021
e82c6f2
为了能运行,不 Resume Long Lived Operaiton
aalenliang Sep 8, 2021
9030be6
初始化 SyncEngine 时,支持不开始同步
aalenliang Sep 9, 2021
7c0020f
避免为 notification 增加多个 obsever
aalenliang Sep 9, 2021
4ef748b
增加 debug 输出
aalenliang Sep 9, 2021
0e14130
Push 数据增加 debug
aalenliang Sep 9, 2021
b7d11bc
增加保存的记录数量
aalenliang Sep 9, 2021
8c444b5
恢复 add long lived operation
aalenliang Sep 9, 2021
4cdb79f
Support pause sync object
aalenliang Apr 8, 2022
80ffa3e
注释掉 unregisterLocalDatabase 方法
aalenliang Apr 8, 2022
10f7833
恢复注释的代码
aalenliang Apr 8, 2022
8939fc9
Cancel fetched long-lived operation if it is not finished
TomLiu Apr 28, 2022
72c26f8
临时更新 log 方式
TomLiu Apr 30, 2022
045af2a
Revert "临时更新 log 方式"
TomLiu May 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Example/IceCream_Example/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 33 additions & 4 deletions IceCream/Classes/DatabaseManagerProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ protocol DatabaseManager: class {

func createCustomZonesIfAllowed()
func startObservingRemoteChanges()
func stopObservingRemoteChanges()
func startObservingTermination()
func stopObservingTermination()
func createDatabaseSubscriptionIfHaveNot()
func registerLocalDatabase()
func unregisterLocalDatabase()

func cleanUp()
}
Expand All @@ -59,10 +62,27 @@ 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 fetched long-lived operation")
self.database.add(modifyOp)
} else {
self.container.add(modifyOp)
}
self.container.add(modifyOp)
}
})
}
Expand All @@ -77,6 +97,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
Expand Down Expand Up @@ -109,25 +133,30 @@ 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)
for chunk in chunkedRecords {
self.syncRecordsToCloudKit(recordsToStore: chunk, recordIDsToDelete: recordIDsToDelete, completion: completion)
}
default:
print("🍦 Modify record unhandled completion: \(error.debugDescription)")
return
}
}


print("🍦 Add operation: syncRecordsToCloudKit store \(recordsToStore.count) \(recordsToStore.first?.recordType ?? "unknown") objects, delete \(recordIDsToDelete.count) objects")
database.add(modifyOpe)
}

Expand Down
44 changes: 41 additions & 3 deletions IceCream/Classes/PrivateDatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -40,27 +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")
database.add(changesOperation)
}

Expand Down Expand Up @@ -90,7 +97,8 @@ final class PrivateDatabaseManager: DatabaseManager {
return
}
}


print("🍦 Add operation: createCustomZonesIfAllowed")
database.add(modifyOp)
}

Expand All @@ -110,6 +118,7 @@ final class PrivateDatabaseManager: DatabaseManager {
self.subscriptionIsLocallyCached = true
}
createOp.qualityOfService = .utility
print("🍦 Add operation: createDatabaseSubscriptionIfHaveNot")
database.add(createOp)
#endif
}
Expand All @@ -125,6 +134,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
Expand All @@ -133,12 +154,21 @@ 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)
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
Expand All @@ -147,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)
Expand All @@ -162,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
}
}
Expand All @@ -190,7 +227,8 @@ final class PrivateDatabaseManager: DatabaseManager {
}
callback?(error)
}


print("🍦 Add operation: fetchChangesInZones")
database.add(changesOp)
}
}
Expand Down
24 changes: 23 additions & 1 deletion IceCream/Classes/PublicDatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -62,6 +74,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) {
Expand Down Expand Up @@ -89,7 +109,8 @@ final class PublicDatabaseManager: DatabaseManager {
break
}
}


print("🍦 Add operation: excuteQueryOperation")
database.add(queryOperation)
}

Expand All @@ -108,6 +129,7 @@ final class PublicDatabaseManager: DatabaseManager {

}
createOp.qualityOfService = .utility
print("🍦 Add operation: createSubscriptionInPublicDatabase")
database.add(createOp)
#endif
}
Expand Down
Loading