Ответ
Property Wrapper — это механизм в Swift, который позволяет инкапсулировать общую логику работы с getter/setter свойства в переиспользуемый тип. Он объявляется с помощью атрибута @propertyWrapper.
Основная идея: Вынести стандартные операции (валидация, трансформация, синхронизация с хранилищем) из самих свойств в отдельную обертку, что делает код чище и DRY (Don't Repeat Yourself).
Структура Property Wrapper:
@propertyWrapper // 1. Объявление атрибута
struct WrapperName<T> {
// 2. Хранимое приватное значение
private var value: T
// 3. Обязательное свойство wrappedValue
var wrappedValue: T {
get { value }
set { value = newValue } // Здесь можно добавить логику
}
// 4. Инициализаторы (опционально)
init(wrappedValue: T) {
self.value = wrappedValue
}
}
// 5. Использование
struct MyStruct {
@WrapperName var myProperty: String = "default" // Логика из WrapperName применяется к myProperty
}
Практический пример: обертка для автоматического обновления UI в UserDefaults
import SwiftUI
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
let userDefaults: UserDefaults
var wrappedValue: T {
get {
userDefaults.object(forKey: key) as? T ?? defaultValue
}
set {
userDefaults.set(newValue, forKey: key)
// Важно: Для SwiftUI можно уведомить об изменении
// objectWillChange.send() если обертка внутри ObservableObject
}
}
}
// Использование в настройках приложения
class SettingsViewModel: ObservableObject {
private let defaults = UserDefaults.standard
@UserDefault(key: "isDarkMode", defaultValue: false, userDefaults: .standard)
var isDarkMode: Bool
@UserDefault(key: "userName", defaultValue: "Гость", userDefaults: .standard)
var userName: String
}
// Изменение свойства автоматически сохраняется в UserDefaults
let settings = SettingsViewModel()
settings.isDarkMode = true // Значение сразу записывается в UserDefaults
Другие распространенные сценарии использования:
- Валидация:
@Email,@PositiveNumber@propertyWrapper struct Email { private var value = "" var wrappedValue: String { get { value } set { // Проверка regex email if newValue.contains("@") { value = newValue } } } } - Трансформация:
@Capitalized,@Trimmed(как в исходном примере). - Логирование: Запись в лог при каждом изменении значения.
- Thread-safety: Обеспечение атомарного доступа к свойству (
@Atomic). - В SwiftUI: Встроенные обертки
@State,@Binding,@Published,@Environment— это property wrappers, которые управляют жизненным циклом и потоком данных в рамках фреймворка.
Дополнительная возможность: projectedValue
Property wrapper может предоставлять дополнительное значение через свойство projectedValue, доступное с префиксом $.
@propertyWrapper struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
var wrappedValue: T {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
// projectedValue предоставляет саму обертку или ее часть
var projectedValue: Clamped { self }
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(range.lowerBound, wrappedValue), range.upperBound)
}
}
struct Game {
@Clamped(0...100) var health: Int = 100
}
var game = Game()
game.health = 150 // Будет зажато до 100
game.health = -20 // Будет зажато до 0
print(game.$health.range) // Используем projectedValue через $ для доступа к диапазону: 0...100