首页 > 解决方案 > 在自动合成的 init 方法中使用非默认参数参数化 @PropertyWrapper?

问题描述

v1。无参数:✅ 按预期工作

通常,我可以像这样创建一个Trimmed 包装器

@propertyWrapper
struct Trimmed {
  private(set) var value: String = ""

  var wrappedValue: String {
    get { value }
    set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
  }

  init(wrappedValue: String) {
    self.wrappedValue = wrappedValue
  }
}

struct Post {
  @Trimmed
  var title: String
  @Trimmed
  var body: String = ""
}

let post = Post(
  title: "  title  ",
  body: "  body  "
)
post.title == "title"
post.body == "body"

title请注意,对于没有默认值的参数(例如)和具有默认值的参数(例如 ),它是如何完美运行的body


v2。一个参数:❌不编译

现在想象一下我不想硬编码.whitespacesAndNewlines,而是允许实现者提供这个值:

@propertyWrapper
struct Trimmed2 { // 
  private(set) var value: String = ""
  let characterSet: CharacterSet // 

  var wrappedValue: String {
    get { value }
    set { value = newValue.trimmingCharacters(in: characterSet) } // 
  }

  init(
    wrappedValue: String,
    characterSet: CharacterSet // 
  ) {
    self.characterSet = characterSet
    self.wrappedValue = wrappedValue
  }
}

struct Post2 {
  @Trimmed2(characterSet: .whitespaces) // ❌ Missing argument for parameter 'wrappedValue' in call
  var title: String
  @Trimmed2(characterSet: .whitespaces)
  var body: String = ""
}
 

我遇到的第一个问题是,title没有默认值的参数无法编译。它要求我添加wrappedValue值。


v3。提供默认属性值:⚠️ 消费者可以省略参数

修复此编译器错误的最简单方法是给它一个默认值,如下所示body

struct Post3 {
  @Trimmed2(characterSet: .whitespaces)
  var title: String = ""
  @Trimmed2(characterSet: .whitespaces)
  var body: String = ""
}

let post3 = Post3(
  // ⚠️ Undesirable since `title` can now be left out of the constructor
  body: "  body  "
) // 
post3.title == "" // ⚠️
post3.body == "body"

但是,现在我失去了强制消费者title通过自动合成的构造函数提供值的能力。


v4。供应默认wrappedValue:⚠️ PropertyWrapper 暴露给消费者

如果我不提供默认值,而是遵守原始错误消息并提供wrappedValue,title现在再次需要,但它有更大的问题。

struct Post4 {
  @Trimmed2(wrappedValue: "", characterSet: .whitespaces)
  var title: String
  @Trimmed2(characterSet: .whitespaces)
  var body: String = ""
}

let post4 = Post4(
  title: .init(wrappedValue: "  title  ", characterSet: .decimalDigits), // ⚠️ PropertyWrapper exposed to consumers
  body: "  body  ")

post4.title == "  title  " // ⚠️ Whitespace no longer removed
post4.body == "body"

更大的问题是 Trimmed 现在向消费者公开,因此他们不能简单地提供 String 值,更糟糕的是,他们可以改变结构的行为(例如,通过提供不同的characterSet)。


v5。提供自定义 init:⚠️ 不再获得自动合成的 init

解决所有这些问题的一种方法是不依赖自动合成的 init,而是提供我们自己的。要解决 v2 中的语法错误,这还需要为title. 这可以通过与body(例如var title: String = "")相同的方式来完成,或者通过向 Trimmed.wrappedValue 添加默认值来完成。两者在功能上是等效的。

@propertyWrapper
struct Trimmed5 {
  private(set) var value: String = ""
  let characterSet: CharacterSet

  var wrappedValue: String {
    get { value }
    set { value = newValue.trimmingCharacters(in: characterSet) }
  }

  init(
    wrappedValue: String = "", // 
    characterSet: CharacterSet
  ) {
    self.characterSet = characterSet
    self.wrappedValue = wrappedValue
  }
}

struct Post5 {
  @Trimmed5(characterSet: .whitespaces)
  var title: String
  @Trimmed5(characterSet: .whitespaces)
  var body: String = ""

  init(title: String, body: String = "") {
    self.title = title
    self.body = body
  }
}

let post5 = Post5(title: "  title  ", body: "  body  ")
post5.title == "title"
post5.body == "body"

但是,我想知道参数化的 PropertyWrapper + 没有默认参数 + 自动合成的构造函数是否可以很好地协同工作。


如果我有一个参数化的 PropertyWrapper,我如何强制消费者在其自动合成的构造函数中为其提供一个值?

(例如,我如何让 v2 ​​编译而不会产生不必要的副作用?)


注意:最初的问题是关于让参数化值的默认属性值按预期工作,但经过一些研究,似乎根本问题是上面提到的问题。因此问题被简化了。

标签: swiftproperty-wrapper

解决方案


如前所述,这目前不支持,并且有一个改变它建议

一种解决方法是使用组合删除任何参数。这类似于@UnitInterval示例,它在@Clamping内部使用 a。

这可能是这样的Trimmed

@propertyWrapper
struct Trimmed {
  private(set) var value: String = ""
  let characterSet: CharacterSet

  var wrappedValue: String {
    get { value }
    set { value = newValue.trimmingCharacters(in: characterSet) }
  }

  init(
    wrappedValue: String,
    characterSet: CharacterSet
  ) {
    self.characterSet = characterSet
    self.wrappedValue = wrappedValue
  }
}

@propertyWrapper
struct TrimmedWhitespace {
  @Trimmed(characterSet: .whitespaces)
  var wrappedValue: String = ""

  init(wrappedValue: String) {
    self.wrappedValue = wrappedValue
  }
}

struct Post {
  @TrimmedWhitespace
  var title: String
  @TrimmedWhitespace
  var body: String = ""
}

let post = Post(title: "  title  ", body: "  body  ")
post.title == "title"
post.body == "body"

请注意,这与 v1 的工作方式相同,其中一个参数具有默认值,而一个参数没有两者都按预期工作。


推荐阅读