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)