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 编译而不会产生不必要的副作用?)
注意:最初的问题是关于让参数化值的默认属性值按预期工作,但经过一些研究,似乎根本问题是上面提到的问题。因此问题被简化了。