Search code examples
iosswiftcloudkitios15

Understanding result of fetching CKRecords with new async/await API


I am a bit confused on how to deal with complex Swift data types, their assignment to variables and accessing the values within. Hopefully someone can clarify the following:

when trying to get data from SwiftUI and CloudKit, and while trying to rewrite a CK function to conform to async/await, I have the following line of code:

let result = try await container.privateCloudDatabase.records(matching: query)

now this line is supposed to get all of the records matching the specified query from the cloud private database and return some CKRecords

The old function did this as follows:

container.privateCloudDatabase.perform(query, inZoneWith: nil) { (rec, err) in 
    for record in rec {
        print("Found this record: \(record)")
    }
}

and this worked great because perform(_:inZoneWith:) would return a CKRecord and an Error, those were "picked apart" in the (rec, err) in statement and passed into the loop to be iterated.

With the new async/await method, I am trying to simply await and assign a variable to all of the records that are found and then iterate the data returned, pick out the CKRecords and perform whatever task I want.

What I am confused with is, when I attempt to pick out the data from the returned result, in several ways, I just get errors. I am not fully understanding how to describe the data structure of the returned values and I think this is the root cause of the issue.

I have tried a few things and have gotten the following error messages:

if I try:

let (result, err)  = try await container.privateCloudDatabase.records(matching: query)

When I use (trying to append the CKRecords to an array of CKRecords I created earlier):

for record in result {
   self.myCKRecordArray.append(record)
}

the error message states specifically:

Cannot convert value of type 'Dictionary<CKRecord.ID, Result<CKRecord, Error>>.Element' (aka '(key: CKRecord.ID, value: Result<CKRecord, Error>)') to expected argument type 'CKRecord'

Which definitely gives me some clues. I believe that my result variable contains a Dictionary<CKRecord.ID, Result<CKRecord, Error>>.Element, or a list of key/value pairs wherein the CKRecord.ID is the key for the Result<CKRecord, Error> value.

This is pretty confusing.. So If I understand that correctly, then:

        for thisDict in result {
            let (realResult, err) = result[thisDict.key]
            print("Found this record: \(realResult)")
        }

should theoretically result in the output of each CKRecord assigned to realResult, right? I just get the error: Type of expression is ambiguous without more context

I tried to change it to valueForKey and also give it more context, but no change from the error message:

let (realResult, err) = result(valueForKey:thisDict.key) as Result<CKRecord, Error>

I just believe that I do not fully understand how to properly access the CKRecord from something that is represented by a: Dictionary<CKRecord.ID, Result<CKRecord, Error>>.Element data structure.

Any insight into understanding is greatly appreciated.

Thank you

Update

Ok, based on the answer from @Shadowrun, if I understand correctly:

The result from:

let result  = try await container.privateCloudDatabase.records(matching: query)

is a tuple type. Which means that it has two elements, the first being the Dictionary of data and the second being the queryCursor.

If I want to iterate over the Dictionary portion of the tuple and get the CKRecord out:

        for rec in result.0 {
            self.myCKRecordArray.append(try rec.value.get())
        }

This does not give me any errors.. Am I understanding this correctly?

2nd update it does work as expected this way.


Solution

  • There’s an equivalence between functions that return a Result<T, E> and functions that either return T or throw an E. And similar equivalence for functions returning tuple of (T?, E?)

    When mapping functions with completion handlers to async functions, they become throwing functions, so should just be:

    let result = try await container….
    

    The error, if any, is thrown