如何在SwiftData中为自定义NSMeasurement维度单元使用ValueTransformer?
问题
尝试在SwiftData中存储NSMeasurement值,使用了两种维度单元:
- 基于现有
UnitLength扩展的自定义单元smoot - 完全自定义的
Radioactivity维度单元(遵循Apple官方指引实现)
自定义单元代码:
extension UnitLength { static let smoot = UnitLength(symbol: "smoot", converter: UnitConverterLinear(coefficient: 1.70180)) } class UnitRadioactivity: Dimension { static let becquerels = UnitRadioactivity(symbol: "Bq", converter: UnitConverterLinear(coefficient: 1)) static let curie = UnitRadioactivity(symbol: "Ci", converter: UnitConverterLinear(coefficient: 3.7e10)) override class func baseUnit() -> Self { UnitRadioactivity.becquerels as! Self } }
通过@Attribute(.transformable(by:结合自定义ValueTransformer实现存储:
@Model class MeasurementModel { @Attribute(.transformable(by: MeasurementTransformer.self)) let measurement: NSMeasurement init(_ measurement: NSMeasurement) { self.measurement = measurement } } final class MeasurementTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { guard let measurement = value as? NSMeasurement else { return nil } do { let data = try NSKeyedArchiver.archivedData(withRootObject: measurement, requiringSecureCoding: true) return data } catch { debugPrint(error.localizedDescription) return nil } } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } do { let measurement = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSMeasurement.self, from: data) return measurement } catch { debugPrint(error.localizedDescription) return nil } } override class func allowsReverseTransformation() -> Bool { true } override class func transformedValueClass() -> AnyClass { NSMeasurement.self } }
转换器对内置单元(如UnitMass)和自定义smoot单元有效,但完全自定义的UnitRadioactivity无效,存储时NSKeyedArchiver报错“无法写入数据,格式不正确”,反转换也失败。
请问为何自定义Dimension子类无法正常进行测量值的转换/归档?如何修改转换逻辑以保留自定义维度单元?
原因与解决方案
问题原因
自定义UnitRadioactivity类未满足Secure Coding的完整要求:
- 未显式遵循
NSSecureCoding协议并声明supportsSecureCoding静态属性,导致NSKeyedArchiver无法确认该类的安全编码兼容性 - 解归档时未指定允许自定义
UnitRadioactivity类,NSKeyedUnarchiver无法识别并还原该类型的单元对象
修复步骤
1. 完善自定义Dimension子类的Secure Coding支持
修改UnitRadioactivity类,显式遵循NSSecureCoding并实现必要的协议要求:
class UnitRadioactivity: Dimension, NSSecureCoding { // 必须声明该静态属性以支持Secure Coding static var supportsSecureCoding: Bool = true static let becquerels = UnitRadioactivity(symbol: "Bq", converter: UnitConverterLinear(coefficient: 1)) static let curie = UnitRadioactivity(symbol: "Ci", converter: UnitConverterLinear(coefficient: 3.7e10)) override class func baseUnit() -> Self { UnitRadioactivity.becquerels as! Self } // 继承父类的编码解码逻辑,无需额外实现,但需显式声明 required init?(coder: NSCoder) { super.init(coder: coder) } override func encode(with coder: NSCoder) { super.encode(with: coder) } }
2. 修改ValueTransformer的解归档逻辑
在反转换方法中,需要同时指定NSMeasurement和自定义UnitRadioactivity类为允许的解归档类型,确保NSKeyedUnarchiver能识别自定义单元:
final class MeasurementTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { guard let measurement = value as? NSMeasurement else { return nil } do { let data = try NSKeyedArchiver.archivedData(withRootObject: measurement, requiringSecureCoding: true) return data } catch { debugPrint(error.localizedDescription) return nil } } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } do { // 使用ofClasses参数,包含NSMeasurement和自定义UnitRadioactivity类 let measurement = try NSKeyedUnarchiver.unarchivedObject( ofClasses: [NSMeasurement.self, UnitRadioactivity.self], from: data ) return measurement } catch { debugPrint(error.localizedDescription) return nil } } override class func allowsReverseTransformation() -> Bool { true } override class func transformedValueClass() -> AnyClass { NSMeasurement.self } }
3. (可选)全局注册自定义Unit类
如果你的应用中多处用到该自定义单元,可在App启动时全局注册,避免重复配置:
// 在App的初始化代码中(如App.swift) NSKeyedUnarchiver.setClass(UnitRadioactivity.self, forClassName: "YourAppName.UnitRadioactivity")
注:YourAppName需替换为你的应用模块名,可通过Bundle.main.bundleIdentifier获取。
原理说明
- 内置单元(如
UnitLength)已经由Apple官方实现了完整的Secure Coding支持,所以扩展的自定义单元能正常归档 - 完全自定义的Dimension子类需要显式声明支持Secure Coding,否则NSKeyedArchiver会因安全限制拒绝归档
- 解归档时必须指定所有涉及的自定义类,否则NSKeyedUnarchiver无法识别并还原对象结构
内容的提问来源于stack exchange,提问作者pkamb
相关产品推荐
相关产品推荐

