v1. No parameters: ✅ works as expected
Normally, I can create a Trimmed wrapper like this:
@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"
Notice how it's working flawlessly for both parameters without default values (e.g. title
), and those with default values (e.g. body
).
v2. One parameter: ❌ Does not compile
Now imagine I don't want to hardcode .whitespacesAndNewlines
, and instead allow the implementor to supply this value:
@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 = ""
}
The first problem I have is that title
, the parameter with no default value, does not compile. It requires that I add the wrappedValue
value.
v3. Supply default property values: ⚠️ Consumers can omit the parameter
The easiest way to fix this compiler error is by giving it a default value like body
does:
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"
However, now I lost the ability to force consumers to supply a title
value via the auto-synthesized constructor.
v4. Supply default wrappedValue
: ⚠️ PropertyWrapper exposed to consumers
If instead of supplying the default value, I comply with the original error message and supply wrappedValue
, title
is now required again, but it has much bigger issues.
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"
The bigger issue is that Trimmed is now exposed to consumers, so they can't simply supply a String value, and worse, they can change the behavior of the struct (e.g. by supplying a different characterSet
).
v5. Supply custom init: ⚠️ No longer get auto-synthesized init
One way to resolve all these issues is to not rely on the auto-synthesized init, and instead supply our own. To solve the syntax error in v2, this also requires supplying a default value for title
. That could either be done via the same way it's done for body
(e.g. var title: String = ""
), or by adding a default value to Trimmed.wrappedValue. Both are functionally equivalent.
@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"
However, I'm wondering if there is a way for parameterized PropertyWrapper + no default arguments + auto synthesized constructors to work nicely together.
If I have a parameterized PropertyWrapper, how do I force a consumer to supply it a value in its auto-synthesized constructor?
(e.g. how do I get v2 to compile without unwanted side effects?)
Note: The original question was about getting default properties values on parameterized values to work as expected, but after doing some research, it seems like the underlying problem is the one mentioned above. Thus the question was simplified.