Cloudkit Ckqueryoperation Doesn't Get All Records

Why will my CKQueryOperation only return a Cursor if the results limit is less than 1000?

I believe that 400 is the the limit for a single operation, so you need to use cursor to get more records, and keep on doing that while returned cursor is not nil.

See how it is done in RxCloudKit library' RecordFetcher -
https://github.com/maxvol/RxCloudKit/blob/master/RxCloudKit/RecordFetcher.swift

Understanding how to correctly execute CKQueryOperation

With the new async pattern it has become much easier to fetch data from CloudKit.

Instead of CKQueryOperation you call records(matching:resultsLimit:) directly and map the result to whatever you like.

A possible error is handed over to the caller.

func queryAllNotes() async throws -> [(title: String, cloudID: String)] {
//set the cloud database to .publicCloudDatabase
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase

let pred = NSPredicate(value: true) //true -> return all records
let query = CKQuery(recordType: "Notes", predicate: pred)

let (notesResults, _) = try await cloudDB.records(matching: query,
resultsLimit: 100)
return notesResults
.compactMap { _, result in
guard let record = try? result.get(),
let noteTitle = record["Title"] as? String else { return nil }
return (title: noteTitle, cloudID: record.recordID.recordName)
}
}

And use it

override func viewDidLoad() {
super.viewDidLoad()

// do additional setup here

// set serachField delegate
searchField.delegate = self

// set tableView delegate and data source
tableView.delegate = self
tableView.dataSource = self

// load all NoteRecords in public cloud db into noteRecords
Task {
do {
noteRecords = try await queryAllNotes()
tableView.reloadData()
} catch {
print(error)
}
}

}

Please watch the related video from WWDC 2021 for detailed information about the async CloudKit APIs and also the Apple examples on GitHub.

Side note:

Rather than a tuple use a struct. Tuples as data source array are discouraged.

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

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.

Swift CloudKit and CKQuery: how to iteratively retrieve records when queryResultBlock returns a query cursor

No need for queryResultBlock in Swift 5.5.

I use this because my CKRecord types are always named the same as their Swift counterparts. You can replace recordType: "\(Record.self)" with your recordType if you want, instead.

public extension CKDatabase {
/// Request `CKRecord`s that correspond to a Swift type.
///
/// - Parameters:
/// - recordType: Its name has to be the same in your code, and in CloudKit.
/// - predicate: for the `CKQuery`
func records<Record>(
type _: Record.Type,
zoneID: CKRecordZone.ID? = nil,
predicate: NSPredicate = .init(value: true)
) async throws -> [CKRecord] {
try await withThrowingTaskGroup(of: [CKRecord].self) { group in
func process(
_ records: (
matchResults: [(CKRecord.ID, Result<CKRecord, Error>)],
queryCursor: CKQueryOperation.Cursor?
)
) async throws {
group.addTask {
try records.matchResults.map { try $1.get() }
}

if let cursor = records.queryCursor {
try await process(self.records(continuingMatchFrom: cursor))
}
}

try await process(
records(
matching: .init(
recordType: "\(Record.self)",
predicate: predicate
),
inZoneWith: zoneID
)
)

return try await group.reduce(into: [], +=)
}
}
}


Related Topics



Leave a reply



Submit