I am using CloudKit to retrieve records from a private database using CKQuery, using the CKQueryOperation.queryResultBlock in an async function. I've found several examples of this using queryCompletionBlock but that has been deprecated and replaced by queryResultBlock, with precious little documentation available as to how to implement it. My function works great as long as a query completion cursor is not returned (<=100 records), but I'm unable to figure out how to iterate it.
Here's the code I'm using:
public func queryRecords(recordType: CKRecord.RecordType, predicate: NSPredicate) async throws -> [CKRecord] {
var resultRecords: [CKRecord] = []
let db = container.privateCloudDatabase
let query = CKQuery(recordType: recordType, predicate: predicate)
let operation = CKQueryOperation(query: query)
let operationQueue = OperationQueue() // for > 100 records
operationQueue.maxConcurrentOperationCount = 1 // for > 100 records
operation.zoneID = zoneID
debugPrint("query for recordType=\(recordType) in zone \(zoneID.zoneName) with predicate \(predicate)")
return try await withCheckedThrowingContinuation { continuation in
operation.queryResultBlock = { result in
switch result {
case .failure(let error):
debugPrint(error)
continuation.resume(throwing: error)
case .success(let ckquerycursor):
debugPrint("successful query completion after \(resultRecords.count) record(s) returned")
if let ckquerycursor = ckquerycursor {
debugPrint("***** received a query cursor, need to fetch another batch! *****")
let newOperation = CKQueryOperation(cursor: ckquerycursor) // for > 100 records
newOperation.queryResultBlock = operation.queryResultBlock // for > 100 records
newOperation.database = db // for > 100 records
operationQueue.addOperation(newOperation) // for > 100 records
}
continuation.resume(returning: resultRecords)
}
}
operation.recordMatchedBlock = { (recordID, result1) in
switch result1 {
case .failure(let error):
debugPrint(error)
case .success(let ckrecord):
resultRecords.append(ckrecord)
}
}
db.add(operation)
}
}
I've attempted to implement code from similar examples but with no success: the code above results in a fatal error "SWIFT TASK CONTINUATION MISUSE" as the line
continuation.resume(returning: resultRecords)
is apparently called multiple times (illegal). The lines commented with "// for > 100 records" represent the code I've added to iterate; everything else works fine for records sets of 100 or less.
Do I need to iteratively call the queryRecords function itself, passing the query cursor if it exists, or is it possible to add the iterative operations to the queue as I've attempted to do here?
If anyone has done this before using queryResultBlock (not deprecated queryCompletionBlock) please help! Thanks!
No need for
queryResultBlockin Swift 5.5.I use this because my
CKRecordtypes are always named the same as their Swift counterparts. You can replacerecordType: "\(Record.self)"with yourrecordTypeif you want, instead.