如何在Core Data中存储自定义Struct?ValueTransformer归档报错
需要将大型自定义Struct存储到Core Data中,使用ValueTransformer实现数据归档时抛出如下错误:
Error Domain=NSCocoaErrorDomain Code=4866 "The data couldn’t be written because it isn’t in the correct format." UserInfo={NSUnderlyingError=0x600002d70ba0 {Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only decode classes that adopt NSSecureCoding. Class '__SwiftValue' does not adopt it." UserInfo={NSDebugDescription=This decoder will only decode classes that adopt NSSecureCoding. Class '__SwiftValue' does not adopt it.}}}
当前编写的ValueTransformer代码如下:
class MyValueTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { guard let model = value as? CoreDataModel else { print("HUI"); return nil } do { let data = try NSKeyedArchiver.archivedData(withRootObject: model, requiringSecureCoding: true) print("value transformer do") return data } catch { print(error) // 捕获上述抛出的错误 print("value transformer catch") return nil } }
相关配置参考:
- 模型结构:

- 模型属性配置:

这个错本质是NSKeyedArchiver开了requiringSecureCoding: true的安全校验之后,只认遵守NSSecureCoding协议的Objective-C类。你存的是Swift原生Struct值类型,直接塞给归档器的时候,系统会自动把它包装成内部私有的__SwiftValue类,这个类根本没实现NSSecureCoding,校验直接就失败了。
不需要把Struct改成OC类,也不用关闭安全编码凑活,用Swift原生的Codable做序列化适配值类型,比NSCoding简单得多,按下面步骤改就行:
- 给
CoreDataModel以及它内部嵌套的所有自定义类型都加上Codable协议遵守,普通的String、Int、Bool、数组、字典这类基础类型默认就支持Codable,不用额外编写编码解码逻辑。 - 重写ValueTransformer逻辑,替换掉原来直接用NSKeyedArchiver归档Struct的实现,改用JSONEncoder/JSONDecoder做正反转换:
// 先定义Transformer的名称,后面Core Data配置里要填完全一致的内容 extension NSValueTransformerName { static let myModelTransformer = NSValueTransformerName("MyValueTransformer") } class MyValueTransformer: ValueTransformer { // 声明正向转换(写入数据)的输出类型为Data override class func transformedValueClass() -> AnyClass { NSData.self } // 声明支持反向转换(读取数据),可以将存储的Data转回模型对象 override class func allowsReverseTransformation() -> Bool { true } // 写入Core Data时调用:模型转Data override func transformedValue(_ value: Any?) -> Any? { guard let model = value as? CoreDataModel else { return nil } do { return try JSONEncoder().encode(model) } catch { print("模型序列化失败:\(error)") return nil } } // 从Core Data读取时调用:Data转模型 override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } do { return try JSONDecoder().decode(CoreDataModel.self, from: data) } catch { print("模型反序列化失败:\(error)") return nil } } }
- 必须在Core Data加载持久化存储之前,提前注册自定义Transformer,否则Core Data找不到对应转换器会直接报错:
ValueTransformer.setValueTransformer(MyValueTransformer(), forName: .myModelTransformer)
- 打开Core Data模型编辑器,找到存储该模型的属性做如下配置:
- 属性类型选择
Transformable - Transformer输入框填写
MyValueTransformer - Custom Class输入框填写Struct名称
CoreDataModel - 如果模型体积较大,建议勾选
Allows External Storage,可降低数据库运行时的内存占用
- 属性类型选择
内容的提问来源于stack exchange,提问作者Павел Кулицкий

