使用 Swift @propertyWrapper 作为动态默认值?

Cla*_*ges 1 swift swift5 property-wrapper

我需要一个 Swift 属性,如果该值尚未设置,则默认为另一个值。

\n\n

可以使用后备存储私有属性来实现。例如,对于num应该默认为 global 的属性defaultNum,它的工作方式如下:

\n\n
var defaultNum = 1\n\nclass MyClass {\n  var num: Int {\n    get { _num ?? defaultNum }\n    set { _num = newValue }\n   }\n\n  private var _num: Int?\n}\n\nlet c = MyClass()\nprint("initial \\(c.num)") // == 1 \xe2\x9c\x85\n\n// changing the default changes the value returned\ndefaultNum = 2\nprint("dynamic \\(c.num)") // == 2 \xe2\x9c\x85\n\n// once the property is set, returns the stored value\nc.num = 5\nprint("base    \\(c.num)") // == 5 \xe2\x9c\x85\n
Run Code Online (Sandbox Code Playgroud)\n\n

这可行,但对于我们代码中的常见模式,每个此类属性都有很多样板。

\n\n

使用 Swift 属性包装器,是否可以更简洁地做到这一点?

\n\n
\n\n

什么不起作用

\n\n

请注意,因为我们希望默认值是动态的,所以静态初始化器将不起作用。例如:

\n\n
var defaultNum = 1\n\nclass MyClass {\n  var num = defaultNum\n}\n\nvar c = MyClass()\ndefaultNum = 2\nprint(c.num) // this == 1, we want the current value of defaultNum, which == 2\n
Run Code Online (Sandbox Code Playgroud)\n

Cla*_*ges 5

您可以通过创建如下属性包装器来做到这一点:

\n\n
@propertyWrapper\npublic struct Default<T> {\n  var baseValue: T?\n  var closure: () -> T\n\n  // this allows a nicer syntax for single variables...\n  public init(_ closure: @autoclosure @escaping () -> T) {\n    self.closure = closure\n  }\n\n  // ... and if we want to explicitly use a closure, we can.\n  public init(_ closure: @escaping () -> T) {\n    self.closure = closure\n  }\n\n  public var wrappedValue: T {\n    get { baseValue ?? closure() }\n    set { baseValue = newValue }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,您可以对@Default属性使用属性包装器,如下所示:

\n\n
var defaultNum = 1\n\nclass MyClass {\n  @Default(defaultNum)\n  var num: Int\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后您会在实践中看到以下内容:

\n\n
let c = MyClass()\n\n// if we haven\'t set the property yet, it uses the closure to return a default value\nprint("initial \\(c.num)") // == 1 \xe2\x9c\x85\n\n// because we are using a closure, changing the default changes the value returned\ndefaultNum = 2\nprint("dynamic \\(c.num)") // == 2 \xe2\x9c\x85\n\n// once the property is set, uses the stored base value\nc.num = 5\nprint("base    \\(c.num)") // == 5 \xe2\x9c\x85\n
Run Code Online (Sandbox Code Playgroud)\n