SwiftData & CloudKit: Arrays of Codable Structs Causing NSKeyedUnarchiveFromData Error

I have SwiftData models containing arrays of Codable structs that worked fine before adding CloudKit capability. I believe they are the reason I started seeing errors after enabling CloudKit.

Example model:

@Model
final class ProtocolMedication {
    var times: [SchedulingTime] = []  // SchedulingTime is Codable
    // other properties...
}

After enabling CloudKit, I get this error logged to the console:

'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release

CloudKit Console shows this times data as "plain text" instead of "bplist" format. Other struct/enum properties display correctly (I think) as "bplist" in CloudKit Console.

The local SwiftData storage handled these arrays fine - this issue only appeared with CloudKit integration.

What's the recommended approach for storing arrays of Codable structs in SwiftData models that sync with CloudKit?

Do you have a feedback report yet? If not, I’d suggest that you file one and share your report ID here.

For a workaround, you might consider making SchedulingTime a SwiftData model, if that is appropriate, and relating it to ProtocolMedication with a too-many relationship.

You can also consider using Data directly for persistence, and providing a transient (@Transient) property for the access to the struct array (only) in memory. A transient property can't be used in a query though.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I switch to using Data with Transient computed property and it still is showing the same error logged to the console:

import SwiftData

@Model
final class ProtocolMedication {
    var uuid: UUID = UUID()
    var createdAt: Date = Date()
    var frequency: SchedulingFrequency = SchedulingFrequency.atRegularIntervals
    var interval: SchedulingInterval = SchedulingInterval(days: 1)
    var startDate: Date = Date()
    var timesData: Data?
    var dayOfWeekSelection: DayOfWeekSelection = DayOfWeekSelection(days: [1])
    var injectionRotationConfig: InjectionRotationConfig = InjectionRotationConfig()
    var medicationConcentration: MedicationConcentration = MedicationConcentration(value: nil, unit: nil)
    
    var medication: Medication?
    @Relationship(deleteRule: .cascade, inverse: \ScheduledDose.protocolMed)
    var _scheduledDoses: [ScheduledDose]?
    @Relationship(deleteRule: .cascade, inverse: \DoseLog.protocolMed)
    var _doseLogs: [DoseLog]?
    
    @Transient
    var times: [SchedulingTime] {
        get {
            guard let data = timesData else { return [] }
            return (try? PropertyListDecoder().decode([SchedulingTime].self, from: data)) ?? []
        }
        set {
            timesData = try? PropertyListEncoder().encode(newValue)
        }
    }
    
    var scheduledDoses: [ScheduledDose] {
        _scheduledDoses ?? []
    }
    
    var doseLogs: [DoseLog] {
        _doseLogs ?? []
    }
    
    init(frequency: SchedulingFrequency, interval: SchedulingInterval = SchedulingInterval(days: 1), startDate: Date, times: [SchedulingTime] = [], dayOfWeekSelection: DayOfWeekSelection = DayOfWeekSelection(days: [1]), injectionRotationConfig: InjectionRotationConfig = InjectionRotationConfig(), medicationConcentration: MedicationConcentration, medication: Medication) {
        self.frequency = frequency
        self.interval = interval
        self.startDate = startDate
        self.times = times
        self.dayOfWeekSelection = dayOfWeekSelection
        self.injectionRotationConfig = injectionRotationConfig
        self.medicationConcentration = medicationConcentration
        self.medication = medication
    }
}

Unrelated but times is a computed property so no need to mark it as Transient. You only need to do that for stored properties that shouldn't be persisted.

SwiftData & CloudKit: Arrays of Codable Structs Causing NSKeyedUnarchiveFromData Error
 
 
Q