【问题标题】:Implement RawRepresentable on UnitMass在 UnitMass 上实现 RawRepresentable
【发布时间】:2021-04-24 01:26:03
【问题描述】:

我正在尝试在Measurement<UnitMass>UnitMass 上实现RawRepresentable,以便用@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") }
}

我该怎么做?我使用 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包装类型,或者放弃使用AppStorage的想法。您不能将 UnitMassRawRepresentable 一致。
  • unitOfMeasure 的唯一目的似乎是表示weightOverwrite 的单位,但weightOverwrite 已经是Measurement&lt;UnitMass&gt;,其中包含单位信息。你不是只需要其中一个来符合RawRepresentable吗?您可以将weightOverwrite 存储在用户默认值中,unitOfMeasure 可以只返回weightOverwrite.unit,对吧?
  • 有点。计算的属性直接映射到配置 UI。用户可以选择unitOfMeasure直接影响weightOverwrite的单位。

标签: swift decorator nsuserdefaults


【解决方案1】:

这就是你不能将UnitMassRawRepresentable 一致的原因。

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

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

如果UnitMass符合RawRepresentableUnitMass的子类也符合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)
    }
}

或者,将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) }
}

【讨论】:

  • 我实际上想出了类似于您上一个代码 sn-p 的东西。但是我怎样才能把它变成一个绑定呢?
  • @LuMa Binding 有一个 getset 初始化器:developer.apple.com/documentation/swiftui/binding/…
  • 我明白,但这给了我Cannot use instance member 'weightOverwrite' within property initializer; property initializers run before 'self' is available。太糟糕了,因为我真的更喜欢那个解决方案。
  • @LuMa 啊,那就改成lazy var吧。
  • 我认为我们已经非常接近一个好的解决方案了,但是这对 SwiftUI 领域来说太深了,我不太熟悉,所以我恐怕帮不了你了那:(
【解决方案2】:

您可以扩展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 { }

【讨论】:

    猜你喜欢
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 2020-03-27
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 1970-01-01
    相关资源
    最近更新 更多