Search code examples
iosswiftcloudkit

CloudKit on iOS won't fetch all of my desired records


My api for using CloudKit to fetch records for my app, isn't working right anymore. It only started doing this a week or two ago. It fetches fine from the public database, just not the private one.

For example, I have a record type called "Part" that contains some values, including a "datestamp" field. The first time the app is launched, it's supposed to fetch all of the records for Part (and others) in the private database with CKQueryOperation, and then on subsequent launches, it fetches any new ones with CKFetchRecordZoneChangesOperation. In my personal iCloud account, I have over 1,000 of these records. But the query operation and the fetch changes operation are only able to fetch the exact same 107 records and no more. This isn't an issue with the resultLimits and I've implemented checking for a CKQueryCursor. As an experiment, I gave it a predicate to only fetch records with a datestamp of Jan 2018 or later - it returned 0 records. There's another record type that has 12 records in my database, but only 1 is syncing.

I designed my syncing class to be a cross platform API. It works on my iOS, macOS and tvOS targets. The macOS target syncs just fine, even though it's using the exact same code line for line. And the iOS and tvOS has worked fine for almost 2 years. Aside from now using the class inside an NSOperation subclass, almost nothing has changed. I've tried on multiple iPhones, iPads, iOS Simulator instances, iOS versions, Xcode versions. I've tried a different iCloud account. I don't know what the problem is. But this bug is holding me up from releasing a much needed update. I thought maybe it's just a bug on Apple's side in the development environment and will work fine on the public release, but I'm a afraid of disabling all of my users.

 protocol CloudData {
    var privateDatabase : CKDatabase { get }
 }

 extension CloudData {
    var privateDatabase : CKDatabase {
       return CKContainer (identifier: "iCloud...").privateDatabase
    }
 }

 final class CloudSync : CloudData {

    func initialSync {

         ...

         // Create an operation to fetch all PARTS
        let partQuery = CKQuery (recordType: "Part", predicate: NSPredicate(value: true))
        let partOperation = CKQueryOperation (query: partQuery)
        partOperation.recordFetchedBlock = { partRecords += [$0] }
        partOperation.zoneID = zoneID
        partOperation.queryCompletionBlock = { cursor, error in

            if let cursorObj = cursor {

                print("Initial Sync - Cursor Found Parts")

                let newOp = CKQueryOperation (cursor: cursorObj)
                newOp.recordFetchedBlock = partOperation.recordFetchedBlock
                newOp.queryCompletionBlock = partOperation.queryCompletionBlock
                self.privateDatabase.add(newOp)

                return
            }
            self.progress.completedUnitCount += 1
            print ("Initial Sync - Part Objects Fetched")


        }

        partOperation.database = privateDatabase

        ...
    }

 }

Solution

  • I discovered the issue and it's not a CloudKit bug. It's actually something kind of dumb on my part. In my iCloud entitlements file, I had a line that explicitly set the iCloud session to the "Production" environment instead of the "Development" one. I don't know why I had that there; I must have been debugging something in the production public store and just forgot to undo it when I was done.

    Regardless though, I think it should still have worked just fine. It may not have fetched ALL of my private records since most of them are in the development environment, but I had a few that I saved to the production environment too, when I was doing the debugging. Also, I don't understand why the iOS Simulator couldn't get records from EITHER of the databases when it was going through the production environment.

    But as long as it's working now, that's fine with me.