jazzedge
11/29/2017 - 8:24 PM

Swift - CloudKit - Handling Notification of Updated Records

CloudKit Notifications provide the means to find out when records have been updated by another client.

See: https://www.whatmatrix.com/blog/a-guide-to-cloudkit-how-to-sync-user-data-across-ios-devices/

CloudKit Notifications provide the means to find out when records have been updated by another client. However, network conditions and performance constraints can cause individual notifications to be dropped, or multiple notifications to intentionally coalesce into a single client notification. Since CloudKit’s notifications are built on top of the iOS notification system, you have to be on the lookout for these conditions.

However, CloudKit gives you the tools you need for this.

Rather than relying on individual notifications to give you detailed knowledge of what change an individual notification represents, you use a notification to simply indicate that something has changed, and then you can ask CloudKit what’s changed since the last time you asked. In my example, I do this by using CKFetchRecordZoneChangesOperation and CKServerChangeTokens. Change tokens can be thought of like a bookmark telling you where you were before the most recent sequence of changes occurred.

// Handle receipt of an incoming push notification that something has changed.
private let serverChangeTokenKey = "ckServerChangeToken"
public func handleNotification() {
	// Use the ChangeToken to fetch only whatever changes have occurred since the last
	// time we asked, since intermediate push notifications might have been dropped.
	var changeToken: CKServerChangeToken? = nil
	let changeTokenData = UserDefaults.standard.data(forKey: serverChangeTokenKey)
	if changeTokenData != nil {
		changeToken = NSKeyedUnarchiver.unarchiveObject(with: changeTokenData!) as! CKServerChangeToken?
	}
	let options = CKFetchRecordZoneChangesOptions()
	options.previousServerChangeToken = changeToken
	let optionsMap = [zoneID!: options]
	let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [zoneID!], optionsByRecordZoneID: optionsMap)
	operation.fetchAllChanges = true
	operation.recordChangedBlock = { record in
		self.delegate?.cloudKitNoteRecordChanged(record: record)
	}
	operation.recordZoneChangeTokensUpdatedBlock = { zoneID, changeToken, data in
		guard let changeToken = changeToken else {
			return
		}
			
		let changeTokenData = NSKeyedArchiver.archivedData(withRootObject: changeToken)
		UserDefaults.standard.set(changeTokenData, forKey: self.serverChangeTokenKey)
	}
	operation.recordZoneFetchCompletionBlock = { zoneID, changeToken, data, more, error in
		guard error == nil else {
			return
		}
		guard let changeToken = changeToken else {
			return
		}

		let changeTokenData = NSKeyedArchiver.archivedData(withRootObject: changeToken)
		UserDefaults.standard.set(changeTokenData, forKey: self.serverChangeTokenKey)
	}
	operation.fetchRecordZoneChangesCompletionBlock = { error in
		guard error == nil else {
			return
		}
	}
	operation.qualityOfService = .utility
		
	let container = CKContainer.default()
	let db = container.privateCloudDatabase
	db.add(operation)
}