v1. Нет параметров: ✅ работает должным образом
Обычно я могу создать обрезанную оболочку следующим образом:
@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, а вместо этого использовать свой собственный. Чтобы устранить синтаксическую ошибку в версии 2, также необходимо указать значение по умолчанию для 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 скомпилировать без нежелательных побочных эффектов?)
Примечание. Первоначальный вопрос заключался в том, чтобы значения свойств по умолчанию для параметризованных значений работали должным образом, но после некоторых исследований выяснилось, что основная проблема связана с упомянутой выше. Таким образом вопрос был упрощен.