You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的完整要求:

  1. 未显式遵循NSSecureCoding协议并声明supportsSecureCoding静态属性,导致NSKeyedArchiver无法确认该类的安全编码兼容性
  2. 解归档时未指定允许自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 21:14:55