9

I'm trying to download a batch of records from my iCloud public database using CloudKit and the cursor. The code works fine for the first 3 executions, regardless of how the resultsLimit is set, but the 4th completion block is never executed. If resultsLimit is not set I get 300 records, if it's set to 50 I get 150, if it's set to 5 I get 15...

No error is reported and the block appears to be added but is never executed. Platform is OS X. Here's the code in question:

        let queryOp = CKQueryOperation(query: query)
        queryOp.recordFetchedBlock = self.fetchedDetailRecord
        queryOp.resultsLimit = 5
        queryOp.queryCompletionBlock = { [weak self]
          (cursor: CKQueryCursor!, error: NSError!) -> Void in
          println("comp block called with \(cursor) \(error)")
          if error != nil {
            println("Error on fetch \(error.userInfo)")
          } else {
            if cursor != nil {
              let nextOp = CKQueryOperation(cursor: cursor)
              nextOp.recordFetchedBlock = self!.fetchedDetailRecord
              nextOp.queryCompletionBlock = queryOp.queryCompletionBlock
              nextOp.resultsLimit = 5
              self!.publicDatabase?.addOperation(nextOp)
              println("added next fetch")
            } else {
              self!.fileHandle!.closeFile()
              self!.exportProgressIndicator.stopAnimation(self)
            }
          }
        }

        self.publicDatabase?.addOperation(queryOp)

And here's the results from the console -

459013587628.012 0 459013587628.621 1 459013587628.863 2 459013587629.113 3 459013587629.339 4 comp block called with nil added next fetch 459013587828.552 5 459013587828.954 6 459013587829.198 7 459013587829.421 8 459013587829.611 9 comp block called with nil added next fetch 459013587997.084 10 459013587997.479 11 459013587997.74 12 459013587997.98 13 459013587998.207 14

The big number is just the time between calls to the recordFetchedBlock with the second number being the count of times that block has been called.

I'm stumped...any ideas on how to proceed? Oh, container and publicDatabase are class variables and initialized prior to running the code snippet above.

Oswaldt
  • 91
  • 3

2 Answers2

7

This could be another approach (already updated to Swift 3).

It does not stops after third iteration, but continues until the end

var queryOp = CKQueryOperation(query: query)
queryOp.recordFetchedBlock = self.fetchedARecord
queryOp.resultsLimit = 5
queryOp.queryCompletionBlock = { [weak self] (cursor : CKQueryCursor?, error : Error?) -> Void in
    print("comp block called with \(cursor) \(error)")

    if cursor != nil {
        let nextOp = CKQueryOperation(cursor: cursor!)
        nextOp.recordFetchedBlock = self!.fetchedARecord
        nextOp.queryCompletionBlock = queryOp.queryCompletionBlock
        nextOp.resultsLimit = queryOp.resultsLimit

        //this is VERY important
        queryOp = nextOp

        self!.publicDB.add(queryOp)
        print("added next fetch")
    }

}

self.publicDB.add(queryOp)

I successfully use it to retrieve hundreds of records from CloudKit

carmine
  • 1,597
  • 2
  • 24
  • 33
  • I had the same exact issue and your comment 'this is VERY important' solved the issue. All I had to add was that single line you stressed its importance. You made my day. Thanks! – tsuyoski Aug 15 '18 at 16:04
6

The way you're defining nextOp inside of your queryCompletionBlock scope is causing a problem after three iterations of that block. If you break the creation of the CKQueryOperation out into its own method your problem goes away.

Here is the code:

func fetchedDetailRecord(record: CKRecord!) -> Void {
    println("Retrieved record: \(record)")
}

func createQueryOperation(cursor: CKQueryCursor? = nil) -> CKQueryOperation {
    var operation:CKQueryOperation
    if (cursor != nil) {
        operation = CKQueryOperation(cursor: cursor!)
    } else {
        operation = CKQueryOperation(query: CKQuery(...))
    }
    operation.recordFetchedBlock = self.fetchedDetailRecord
    operation.resultsLimit = 5
    operation.queryCompletionBlock = { [weak self]
        (cursor: CKQueryCursor!, error: NSError!) -> Void in
        println("comp block called with \(cursor) \(error)")
        if error != nil {
            println("Error on fetch \(error.userInfo)")
        } else {
            if cursor != nil {
                self!.publicDatabase?.addOperation(self!.createQueryOperation(cursor: cursor))
                println("added next fetch")
            } else {
                self!.fileHandle!.closeFile()
                self!.exportProgressIndicator.stopAnimation(self) 
            }
        }
    }
    return operation
}

func doQuery() {
    println("Querying records...")
    self.publicDatabase?.addOperation(createQueryOperation())
}
Dave Browning
  • 1,256
  • 7
  • 7
  • 1
    Thanks Dave, apologies for taking so long to respond but your response is dead on. Once I moved that out into it's own function the query ran perfectly. – Oswaldt Aug 10 '15 at 01:57
  • 2
    Thanks! However, it seems operation.cursor = cursor doesn't make the operation to start with the cursor; instead using operation = CKQueryOperation(cursor: cursor) worked for me - thoughts? – hyouuu Oct 04 '15 at 06:58
  • Ahh, good catch. The docs do actually say "When you use a cursor, the contents of the query property are ignored" so I've updated the code sample accordingly. – Dave Browning Oct 05 '15 at 23:03
  • This solution has a major drawback, though: the initial operation will end before all sub operations are completed. Therefore, we can't use it for dependencies between operations :-( I tried using addOperations(:waitUntilFinished) instead of addOperation() but... it stops after three iterations. Any thoughts guys? – Jean Le Moignan May 26 '16 at 22:57