let container = CKContainer.default()let privateDB = container.privateCloudDatabaselet sharedDB = container.sharedCloudDatabase// Use a consistent zone ID across the user's devices// CKCurrentUserDefaultName specifies the current user's ID when creating a zone IDlet zoneID =CKRecordZoneID(zoneName:"Todos", ownerName: CKCurrentUserDefaultName)// Store these to disk so that they persist across launchesvar createdCustomZone =falsevar subscribedToPrivateChanges =falsevar subscribedToSharedChanges =falselet privateSubscriptionId ="private-changes"let sharedSubscriptionId ="shared-changes"
func fetchChanges(in databaseScope: CKDatabaseScope, completion: @escaping () -> Void) {
switch databaseScope {
case .private:
fetchDatabaseChanges(database: self.privateDB, databaseTokenKey: "private", completion: completion)
case .shared:
fetchDatabaseChanges(database: self.sharedDB, databaseTokenKey: "shared", completion: completion)
case .public:
fatalError()
}
}
func fetchDatabaseChanges(database: CKDatabase, databaseTokenKey: String, completion: @escaping () -> Void) {
var changedZoneIDs: [CKRecordZoneID] = []
let changeToken = … // Read change token from disk
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: changeToken)
operation.recordZoneWithIDChangedBlock = { (zoneID) in
changedZoneIDs.append(zoneID)
}
operation.recordZoneWithIDWasDeletedBlock = { (zoneID) in
// Write this zone deletion to memory
}
operation.changeTokenUpdatedBlock = { (token) in
// Flush zone deletions for this database to disk
// Write this new database change token to memory
}
operation.fetchDatabaseChangesCompletionBlock = { (token, moreComing, error) in
if let error = error {
print("Error during fetch shared database changes operation", error)
completion()
return
}
// Flush zone deletions for this database to disk
// Write this new database change token to memory
self.fetchZoneChanges(database: database, databaseTokenKey: databaseTokenKey, zoneIDs: changedZoneIDs) {
// Flush in-memory database change token to disk
completion()
}
}
operation.qualityOfService = .userInitiated
database.add(operation)
}
func fetchZoneChanges(database: CKDatabase, databaseTokenKey: String, zoneIDs: [CKRecordZoneID], completion: @escaping () -> Void) {
// Look up the previous change token for each zone
var optionsByRecordZoneID = [CKRecordZoneID: CKFetchRecordZoneChangesOptions]()
for zoneID in zoneIDs {
let options = CKFetchRecordZoneChangesOptions()
options.previousServerChangeToken = … // Read change token from disk
optionsByRecordZoneID[zoneID] = options
}
let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zoneIDs, optionsByRecordZoneID: optionsByRecordZoneID)
operation.recordChangedBlock = { (record) in
print("Record changed:", record)
// Write this record change to memory
}
operation.recordWithIDWasDeletedBlock = { (recordId) in
print("Record deleted:", recordId)
// Write this record deletion to memory
}
operation.recordZoneChangeTokensUpdatedBlock = { (zoneId, token, data) in
// Flush record changes and deletions for this zone to disk
// Write this new zone change token to disk
}
operation.recordZoneFetchCompletionBlock = { (zoneId, changeToken, _, _, error) in
if let error = error {
print("Error fetching zone changes for \(databaseTokenKey) database:", error)
return
}
// Flush record changes and deletions for this zone to disk
// Write this new zone change token to disk
}
operation.fetchRecordZoneChangesCompletionBlock = { (error) in
if let error = error {
print("Error fetching zone changes for \(databaseTokenKey) database:", error)
}
completion()
}
database.add(operation)
}
// obtain the metadata from the CKRecord
let data = NSMutableData()
let coder = NSKeyedArchiver.init(forWritingWith: data)
coder.requiresSecureCoding = true
record.encodeSystemFields(with: coder)
coder.finishEncoding()
// store this metadata on your local object
yourLocalObject.encodedSystemFields = data
// set up the CKRecord with its metadata
let coder = NSKeyedUnarchiver(forReadingWith: yourLocalObject.encodedSystemFields!)
coder.requiresSecureCoding = true
let record = CKRecord(coder: coder)
coder.finishDecoding()
// write your custom fields...