在 UnitMass 上实施 RawRepresentable

Implement RawRepresentable on UnitMass

我正在尝试在 Measurement<UnitMass>UnitMass 上实现 RawRepresentable,以便用 @AppStorage 装饰器替换以下代码:

var unitOfMeasure: UnitMass {
    get { AppSettings.defaults.string(forKey: "unitOfMeasure").flatMap { UnitMass.fromSymbol(rawValue: [=10=]) }! }
    set { AppSettings.defaults.set(newValue.symbol, forKey: "unitOfMeasure") }
}

var weightOverwrite: Measurement<UnitMass> {
    get { .init(value: AppSettings.defaults.double(forKey: "weightOverwrite"), unit: unitOfMeasure) }
    set { AppSettings.defaults.set(newValue.value, forKey: "weightOverwrite") }
}

我该怎么做?我有点用 JSONEncoder/JSONDecoder:

实现了它
extension Measurement: RawRepresentable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
            let result = try? JSONDecoder().decode(Measurement.self, from: data)
        else {
            return nil
        }
        self = result
    }

    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
            let result = String(data: data, encoding: .utf8)
        else {
            return "{}"
        }
        return result
    }
}

但是我没有为 UnitMass:

extension UnitMass: RawRepresentable {
    public init?(rawValue: String) {
        for unitMass in UnitMass.allCases where rawValue == unitLength.symbol {
            self = unitLength
        }
        
        return nil
    }
}

我得到 Designated initializer cannot be declared in an extension of 'UnitMass'。我做错了什么?

这就是为什么你不能让 UnitMass 符合 RawRepresentable

RawRepresentable 要求符合 classes 必须有一个 init(rawValue:) 初始化器。

UnitMass 不是 final,所以它可以有子class。

UnitMass 的子class 也符合 RawRepresentable 如果 UnitMass 符合 RawRepresentable,那么它们也必须有 init(rawValue:) .

子class中的init(rawValue:)如何实现?请注意,它们不能只继承 UnitMass 中的实现,因为子 class 可能有自己的存储属性,需要在初始化程序中初始化。

所以您的扩展程序需要 UnitMass 的所有子 classes 来实现这个新的初始化程序。好吧,扩展不应该添加需求——它们应该添加功能!

即使扩展 可以 做到这一点,您去 UnitMass 的每个子 class 并添加 [= 的实现也是不切实际的=16=] :)

无论如何,这里有一些解决方法:

使用包装器 class:

class MyUnitMass: RawRepresentable {
    let unitMass: UnitMass
    
    var rawValue: String {
        unitMass.symbol
    }
    
    required init?(rawValue: String) {
        // assuming fromSymbol actually uses the correct converter
        unitMass = UnitMass.fromSymbol(rawValue: rawValue)
    }
}

或者,将UserDefaults中的UnitMass保存为Data而不是String,因为UnitMass符合NSSecureCoding

let data = try NSKeyedArchiver.archivedData(withRootObject: UnitMass.grams, requiringSecureCoding: false)

// save "data" to UserDefaults instead

let unitMass = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! UnitMass

这样做的好处是还可以对 converter 进行编码,而无需在 fromSymbol 中对其进行硬编码(大概是您现在正在做的事情)。

另请注意,如果您仅将 unitOfMeasure 设置用作 weightOverwrite 的单位,则应将 weightOverwrite 保存在用户默认值中,并声明 unitOfMeasure像这样:

@AppStorage("hello", store: UserDefaults.standard)
var weightOverwrite: Measurement<UnitMass> = Measurement(value: 1, unit: .grams)

var unitOfMeasure: UnitMass {
    get { weightOverwrite.unit }
    set { weightOverwrite.convert(to: newValue) }
}

您可以扩展 RawReprresentable 本身,以便 UnitMass 开始自动符合它。

extension RawRepresentable where Self: NSSecureCoding {
    public var rawValue: String {
        let data = try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
        return String(data: data!, encoding: .utf8)!

    }
    public init?(rawValue: String) {
        if let unitMass = (try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(Data(rawValue.utf8))) as? Self {
            self = unitMass
        } else {
            return nil
        }
    }
}

extension UnitMass: RawRepresentable { }