Help Rescuing SwiftData Schema with Non-Optional Transformables

I currently have a schema in production (cloudKit and local files) containing non-optional transformable values, e.g.

@Attribute(.transformable(by: TestTransformer.self))
        var number: TestTransformable = TestTransformable.init(value: 100)

Unfortunately, this is preventing any migration from succeeding (documented at length in FB22151570).

Briefly summarized, any migration from a Schema containing non-optional transformable values fails between willMigrate and didMigrate with the error "Can't find model for source store". This occurs for all migrations, including lightweight with a migration plan, lightweight without a plan, and custom migrations. Worst of all, this also prevents migration to optional transformable values, or the elimination of the transformable value entirely, leaving us completely stuck.

(note: optional transformable values only work when they have a default value set to nil, otherwise even these have issues migrating)

We already have features being blocked by this issue, and would like to preserve user-data while restoring our ability to move forwards with database.

Are there any known workarounds for using SwiftData (+CloudKit) when schema migration is non-operational?

Thank you for the post and the Feedback provided.

The source store issue in this case should be resolved by adding the NSSecureCoding conformance to TestTransformer.

Thanks for the research - unfortunately, this still doesn't allow me to migrate databases.

If I create a new SwiftData database with the NSSecureCoding TestTransformable, I can save, load, and migrate it successfully.

However, if I have an existing SwiftData database that wasn't created with the NSSecureCoding TestTransformable, things are strange: Once the codebase is recompiled with the NSSecureCoding conformance applied, I can load the old database successfully. However, it still fails in the same way when I go to migrate the database.

To be clear - NSSecureCoding fixes save/load/migrate on a new database, but when applied to an existing database (such as the one I have deployed to iCloud), only save/load works, and migration still fails.

Below are the changes made to test NSSecureCoding:

@objc(TestTransformable)
//toggle between these declarations for testing
//final nonisolated class TestTransformable: NSObject, Codable, Sendable {
final nonisolated class TestTransformable: NSObject, Codable, Sendable, NSSecureCoding {
    static let supportsSecureCoding: Bool = true
    let value: Int
    init(value: Int) {
        self.value = value
    }
    required init?(coder: NSCoder) {
        self.value = coder.decodeInteger(forKey: "value")
    }
func encode(with coder: NSCoder) {
        coder.encode(value, forKey: "value")
    }
}

@objc(TestTransformer)
class TestTransformer: ValueTransformer {
    override class func allowsReverseTransformation() -> Bool { true }
    override class func transformedValueClass() -> AnyClass {
        //It is unclear to me whether this should be NSData.self or TestTransformable.self
        //However, for the purposes of this test, both values behave identically.
        //NSData.self
        TestTransformable.self
    }
    override func transformedValue(_ value: Any?) -> Any? {
        guard let transformable = value as? TestTransformable else { return nil }
        return try? JSONEncoder().encode(transformable)
    }
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }
        return try? JSONDecoder().decode(TestTransformable.self, from: data)
    }
}

For the sake of completion, I'll outline the process used to save/load/migrate as well:

//save
        let schema = Schema(versionedSchema: badSchema_v1.self)
        let config = ModelConfiguration(url: working_url, cloudKitDatabase: .none)
        let container = try ModelContainer(for: schema, configurations: config)
        try container.mainContext.save()

//load
        let schema = Schema(versionedSchema: badSchema_v1.self)
        let config = ModelConfiguration(url: working_url, cloudKitDatabase: .none)
        let container = try ModelContainer(for: schema, configurations: config)

//migrate
        let schema = Schema(versionedSchema: badSchema_v2.self)
        let config = ModelConfiguration(url: working_url, cloudKitDatabase: .none)
        let _ = try ModelContainer(for: schema, migrationPlan: badSMP.self, configurations: config)
Help Rescuing SwiftData Schema with Non-Optional Transformables
 
 
Q