Search code examples
swiftcloudkit

How to strictly limit the number of CloudKit results with resultsLimit


Let me set up a scene for you: I have a CloudKit DB with 1000 records. And I need to fetch 150.

Problem #1: By default, CloudKit returns 100 records and a cursor. If I use that cursor to fetch the next batch, I'll end up fetching 200 records. And even that is not guaranteed, which brings us to,

Problem #2: The 100 records number is not fixed. CloudKit will decide how many records to return, depending on the current load of CloudKit's servers. So I can get, say, only 30 records on the first query and 30 more on the second query = 60.

Problem #3: If I fetch the next batch in a loop, I'll end up with all 1000 records.

Here is my looping code, just for the test I've set a resultsLimit: 75.

func fetchRecords() async throws -> [CKRecord] {
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "MyRecordType", predicate: predicate)
        query.sortDescriptors = [NSSortDescriptor(key: "datePublished", ascending: false)]
        
        var records: [CKRecord] = []
        var (matchResults, queryCursor) = try await CKContainer(identifier: "iCloud.MyDB").publicCloudDatabase.records(matching: query, resultsLimit: 75)
        
        records.append(contentsOf: matchResults
                                    .compactMap { _, result in try? result.get()}
                                    .compactMap { $0 })

        while let cursor = queryCursor {
            (matchResults, queryCursor) = try await CKContainer(identifier: "iCloud.MyDB").publicCloudDatabase.records(continuingMatchFrom: cursor, resultsLimit: 75)
            records.append(contentsOf: matchResults
                                        .compactMap { _, result in try? result.get()}
                                        .compactMap { $0 })
        }
        
        print("Will return \(records.count) CloudKit records")
        return records
    }

This method will return all 1000 records, fetching them by 75 / query.

Is there a way to strictly ask CloudKit to deliver 150 records (in batches or not, but no more than 150) or should I use the DB inefficiently and fetch as much as I can (like 200) from CloudKit, and manually pick 150 of them in Swift code afterwards?


Solution

  • The resultsLimit is the maximum number of rows that CloudKit will return. Instead of hardcoding 75 as the resultsLimit in the while loop. Adjust that number based on how many more results you still need to obtain. And break out of the while loop once you reach the desired goal.

    Let's say there is a variable named maxRows and it's set to 150 or any other desired number of rows.

    while let cursor = queryCursor && records.count < maxRows {
        let limit = maxRows - results.count
        (matchResults, queryCursor) = try await CKContainer(identifier: "iCloud.MyDB").publicCloudDatabase.records(continuingMatchFrom: cursor, resultsLimit: limit)
        records.append(contentsOf: matchResults
                                    .compactMap { _, result in try? result.get()}
                                    .compactMap { $0 })
    }