在 UnitMass 上实现 RawRepresentable

LuM*_*uMa 1 decorator nsuserdefaults swift

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

var unitOfMeasure: UnitMass {
    get { AppSettings.defaults.string(forKey: "unitOfMeasure").flatMap { UnitMass.fromSymbol(rawValue: $0) }! }
    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") }
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?我使用 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
    }
}
Run Code Online (Sandbox Code Playgroud)

但我未能为 UnitMass 做到这一点:

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

我明白了Designated initializer cannot be declared in an extension of 'UnitMass'。我究竟做错了什么?

Swe*_*per 5

这就是为什么你不能遵守UnitMass的原因RawRepresentable

RawRepresentable要求符合规范的类必须有一个init(rawValue:)初始化程序。

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

的子类UnitMass也符合RawRepresentable如果UnitMass符合RawRepresentable,则它们也必须具有init(rawValue:)

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

因此,您的扩展需要 的所有子类UnitMass来实现这个新的初始化程序。嗯,扩展不应该添加需求 - 它们应该添加功能!

即使扩展可以做到这一点,你去每个子类UnitMass并添加一个实现也是不切实际的init(rawValue:):)

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

使用包装类:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,将UnitMassin保存UserDefaults为 而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
Run Code Online (Sandbox Code Playgroud)

这样做的优点是还可以对 进行编码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) }
}
Run Code Online (Sandbox Code Playgroud)