Search code examples
swiftcloudkit

CloudKit Query Operation only returns 300 results


I am currently setting up CloudKit as a replacement to Parse and need to download all of my user records. I currently have around 600 records but I am only receiving 300.

I'm using a custom record zone called "User" rather than the default "Users" record zone as this app will only ever be tied to one appID.

The code I am using is based on the answer to the below question but it's not working for me. It seems that the query operation does not run when the cursor is nil as the print(userArray) is never called. Thanks in advance for your help!

CKQuery from private zone returns only first 100 CKRecords from in CloudKit

func queryAllUsers() {

    let database = CKContainer.defaultContainer().privateCloudDatabase
    let query = CKQuery(recordType: "User", predicate: NSPredicate(value: true))
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.recordFetchedBlock = self.createUserObject

    queryOperation.queryCompletionBlock = { cursor, error in

        if cursor != nil {
            print("there is more data to fetch")
            let newOperation = CKQueryOperation(cursor: cursor!)
            newOperation.recordFetchedBlock = self.createUserObject
            newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
            database.addOperation(newOperation)

        } else {

            print(userArray) //Never runs
        }
    }

    database.addOperation(queryOperation)

}

func createUserObject(record: CKRecord) {
    let name = record.objectForKey("Name") as! String!
    let company = record.objectForKey("Company") as! String!
    let dateInductionCompleted = record.objectForKey("DateInductionCompleted") as! NSDate!
    var image = UIImage()

    let imageAsset = record.objectForKey("Image") as! CKAsset!
    if let url = imageAsset.fileURL as NSURL? {
        let imageData = NSData(contentsOfURL:url)
        let mainQueue = NSOperationQueue.mainQueue()
        mainQueue.addOperationWithBlock() {
            image = UIImage(data: imageData!)!
            userArray.append(User(name: name, company: company, image: image, dateInductionCompleted: dateInductionCompleted))
        }
    }
    print(userArray.count)
}

UPDATE

The question has been answered, it was possibly an inherent bug when using a cursor for large queries. The code now works by using a recursive function, working code below:

func queryRecords() {

    let database = CKContainer.defaultContainer().privateCloudDatabase
    let query = CKQuery(recordType: "User", predicate: NSPredicate(value: true))
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.qualityOfService = .UserInitiated
    queryOperation.recordFetchedBlock = populateUserArray

    queryOperation.queryCompletionBlock = { cursor, error in

        if cursor != nil {
            print("There is more data to fetch")
            self.fetchRecords(cursor!)
        }
    }
    database.addOperation(queryOperation)
}


func fetchRecords(cursor: CKQueryCursor?) {

    let database = CKContainer.defaultContainer().privateCloudDatabase
    let queryOperation = CKQueryOperation(cursor: cursor!)
    queryOperation.qualityOfService = .UserInitiated
    queryOperation.recordFetchedBlock = populateUserArray

    queryOperation.queryCompletionBlock = { cursor, error in

        if cursor != nil {
            print("More data to fetch")
            self.fetchRecords(cursor!)

        } else {
            print(userArray)
        }
    }

    database.addOperation(queryOperation)
}

func populateUserArray(record: CKRecord) {

    let name = record.objectForKey("Name") as! String!
    let company = record.objectForKey("Company") as! String!
    let dateInductionCompleted = record.objectForKey("DateInductionCompleted") as! NSDate!
    var image = UIImage()

    let imageAsset = record.objectForKey("Image") as! CKAsset!
    if let url = imageAsset.fileURL as NSURL? {
        let imageData = NSData(contentsOfURL:url)
        let mainQueue = NSOperationQueue.mainQueue()
        mainQueue.addOperationWithBlock() {
            image = UIImage(data: imageData!)!
            userArray.append(User(name: name, company: company, image: image, dateInductionCompleted: dateInductionCompleted))
        }
    }
    print(userArray.count)
}

Solution

  • Could you try setting:

    queryOperation.qualityOfService = .UserInitiated
    

    This will indicate that your user interaction requires the data. Otherwise it could happen that de request is ignored completely.

    As discussed below the actual answer was that you should not re-use completion blocks. Instead you should create a recursive function for fetching the next records from a cursor. A sample of that can be found at: EVCloudKitDao