Alexey Chekanov
Alexey Chekanov

Reputation: 1117

Codable to CKRecord

I have several codable structs and I'd like to create a universal protocol to code them to CKRecord for CloudKit and decode back.

I have an extension for Encodable to create a dictionary:

extension Encodable {

    var dictionary: [String: Any] {
        return (try? JSONSerialization.jsonObject(with: JSONEncoder().encode(self), options: .allowFragments)) as? [String: Any] ?? [:]
    }
}

Then in a protocol extension, I create the record as a property and I try to create a CKAsset if the type is Data.

var ckEncoded: CKRecord? {

    // Convert self.id to CKRecord.name (CKRecordID)
    guard let idString = self.id?.uuidString else { return nil }
    let record = CKRecord(recordType: Self.entityType.rawValue,
                          recordID: CKRecordID(recordName: idString))

    self.dictionary.forEach {

        if let data = $0.value as? Data {
            if let asset: CKAsset = try? ckAsset(from: data, id: idString) { record[$0.key] = asset }
        } else {
            record[$0.key] = $0.value as? CKRecordValue
        }
    }

    return record
}

To decode:

func decode(_ ckRecord: CKRecord) throws {

    let keyIntersection = Set(self.dtoEncoded.dictionary.keys).intersection(ckRecord.allKeys())
    var dictionary: [String: Any?] = [:]

    keyIntersection.forEach {

        if let asset = ckRecord[$0] as? CKAsset {
            dictionary[$0] = try? self.data(from: asset)
        } else {
            dictionary[$0] = ckRecord[$0]
        }
    }

    guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else { throw Errors.LocalData.isCorrupted }
    guard let dto = try? JSONDecoder().decode(self.DTO, from: data) else { throw  Errors.LocalData.isCorrupted }


    do { try decode(dto) }
    catch { throw error }
} 

Everything works forth and back except the Data type. It can't be recognized from the dictionary. So, I can't convert it to CKAsset. Thank you in advance.

Upvotes: 9

Views: 2138

Answers (2)

MadeByDouglas
MadeByDouglas

Reputation: 2835

So I had this problem as well, and wasn't happy with any of the solutions. Then I found this, its somewhat helpful, doesn't handle partial decodes very well though https://github.com/ggirotto/NestedCloudkitCodable

Upvotes: 0

Vlad
Vlad

Reputation: 5928

I have also found there is no clean support for this by Apple so far.

My solution has been to manually encode/decode: On my Codable subclass I added two methods:

/// Returns CKRecord
func ckRecord() -> CKRecord {
    let record = CKRecord(recordType: "MyClassType")
    record["title"] = title as CKRecordValue
    record["color"] = color as CKRecordValue
    return record
}


init(withRecord record: CKRecord) {
    title = record["title"] as? String ?? ""
    color = record["color"] as? String ?? kDefaultColor
}

Another solution for more complex cases is use some 3rd party lib, one I came across was: https://github.com/insidegui/CloudKitCodable

Upvotes: 1

Related Questions