6

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
JerseyDevel
  • 1,334
  • 1
  • 15
  • 34

2 Answers2

3

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

malhal
  • 26,330
  • 7
  • 115
  • 133
Shadowrun
  • 3,572
  • 1
  • 15
  • 13
  • Ok, that makes sense, thank you, but when trying to iterate that result, it is a much more complex data type than just an array of CKRecords: For-in loop requires '(matchResults: [CKRecord.ID : Result], queryCursor: CKQueryOperation.Cursor?)' to conform to 'Sequence' – JerseyDevel Jun 17 '21 at 12:53
  • So result is a tuple type, first element is dictionary, you can iterate over that? Can’t for in a tuple. – Shadowrun Jun 17 '21 at 13:25
  • I have updated my question at the bottom to see if I understand what you are saying properly.. My errors have gone away, I do not have an actual device updated to iOS 15 for testing at the moment to verify operation. I think I got it tho.. – JerseyDevel Jun 17 '21 at 16:21
  • i was able to test and it does work as expected. Thank you for the info – JerseyDevel Jun 17 '21 at 18:41
2

At the risk of being pedantic, may I suggest reading the docs?

https://developer.apple.com/documentation/cloudkit/ckdatabase/3856524-records

func records(matching query: CKQuery, 
    inZoneWith zoneID: CKRecordZone.ID? = nil, 
    desiredKeys: [CKRecord.FieldKey]? = nil, 
    resultsLimit: Int = CKQueryOperation.maximumResults) async throws -> 
        (matchResults: [CKRecord.ID : Result<CKRecord, Error>],     
          queryCursor: CKQueryOperation.Cursor?)

So your result is tuple. Call your result tuple. Then tuple.matchResults is a dictionary keyed by ID whose values are Result objects.

The best way to extract value from a Result object, in my opinion, is to use get. This has the advantage that it either returns a value or throws, and you can cope nicely with that.

James Toomey
  • 5,635
  • 3
  • 37
  • 41
matt
  • 515,959
  • 87
  • 875
  • 1,141