ioscocoa-touchcloudkitcksubscription

CloudKit, after reinstalling an app how do I reset my subscriptions to current status of records?


I'm dealing with the scenario, where a user has previously deleted the app and has now re-installed it.

It was hitting my delta fetch function, which is receiving a lot of old subscription notifications, mostly deletes. But not downloading current records.

I'm now adding code to perform a fetch on each record type to download all the data.

I'd like to reset delta fetch server token, so the app doesn't have to process old subscriptions notifications. However I can't find how to do this, maybe it's not possible.


Solution

  • Are you referring to CKServerChangeToken (documentation) when you say "delta fetch server token"? And are you attempting to sync within the CloudKit private database?

    Assuming that is true, here is an example of how I fetch changes from the private database and keep track of the sync token:

    //MARK: Fetch Latest from CloudKit from private DB
    func fetchPrivateCloudKitChanges(){
      print("Fetching private changes...")
    
      //:::
      let privateZoneId = CKRecordZone.ID(zoneName: CloudKit.zoneName, ownerName: CKCurrentUserDefaultName)
    
      /----
      let options = CKFetchRecordZoneChangesOperation.ZoneOptions()
      options.previousServerChangeToken = previousChangeToken
    
      let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [privateZoneId], optionsByRecordZoneID: [recordZoneID:options])
    
      //Queue up the updated records to process below
      var records = [CKRecord]()
    
      operation.recordChangedBlock = { record in
        records.append(record)
      }
    
      operation.recordWithIDWasDeletedBlock = { recordId, type in
        //Process a deleted record in your local database...
      }
    
      operation.recordZoneChangeTokensUpdatedBlock = { (zoneId, token, data) in
        // Save new zone change token to disk
        previousChangeToken = token
      }
    
      operation.recordZoneFetchCompletionBlock = { zoneId, token, _, _, error in
        if let error = error {
          print(error)
        }
        // Write this new zone change token to disk
        previousChangeToken = token
      }
    
      operation.fetchRecordZoneChangesCompletionBlock = { error in
        if let error = error {
          print(error
        }else{
          //Success! Process all downloaded records from `records` array above...
          //records...
        }
      }
    
      CloudKit.privateDB.add(operation)
    }
    
    //Change token property that gets saved and retrieved from UserDefaults
    var previousChangeToken: CKServerChangeToken? {
      get {
        guard let tokenData = defaults.object(forKey: "previousChangeToken") as? Data else { return nil }
        return NSKeyedUnarchiver.unarchiveObject(with: tokenData) as? CKServerChangeToken
      }
      set {
        guard let newValue = newValue else {
          defaults.removeObject(forKey: "previousChangeToken")
          return
        }
    
        let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
        defaults.set(data, forKey: "previousChangeToken")
      }
    }
    

    Your specific situation might differ a little, but I think this is how it's generally supposed to work when it comes to staying in sync with CloudKit.

    Update

    You could try storing the previousServerChangeToken on the Users record in CloudKit (you would have to add it as a new field). Each time the previousServerChangeToken changes in recordZoneFetchCompletionBlock you would have to save it back to iCloud on the user's record.