Что такое Property Wrapper в Swift и как он работает?

Ответ

Property Wrapper — это механизм в Swift, который позволяет инкапсулировать стандартную логику работы с getter и setter свойства в переиспользуемый тип. Он «оборачивает» значение свойства, добавляя к нему дополнительное поведение при чтении и записи.

Синтаксис и компоненты:

  1. Атрибут @propertyWrapper: Помечает структуру или класс как обертку.
  2. Свойство wrappedValue: Обязательное свойство, которое содержит логику get/set для обернутого значения.
  3. Инициализатор: Может принимать начальное значение.
  4. Свойство projectedValue (опционально): Предоставляет дополнительный API для обернутого свойства (доступ через $).

Пример: Обертка для автоматического триммирования строки.

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

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

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue // Используется setter
    }
}

// Использование
struct UserProfile {
    @Trimmed var username: String
    @Trimmed var email: String
}

var profile = UserProfile(username: "  john_doe  ", email: " email@example.com ")
print(profile.username) // "john_doe" (пробелы удалены)
print(profile.email)    // "email@example.com" (пробел удален)

Пример с projectedValue (логирование изменений):

@propertyWrapper
struct Logged<Value> {
    private var value: Value
    private let name: String

    var wrappedValue: Value {
        get { value }
        set {
            print("Свойство '(name)' изменено: (value) -> (newValue)")
            value = newValue
        }
    }

    var projectedValue: Self { self } // Проецируем саму обертку

    init(wrappedValue: Value, _ name: String = "") {
        self.value = wrappedValue
        self.name = name
    }
}

struct Settings {
    @Logged("volume") var volume: Double = 0.5
}

var settings = Settings()
settings.volume = 0.7 // В консоли: "Свойство 'volume' изменено: 0.5 -> 0.7"
let wrapper = settings.$volume // Доступ к самой обертке через projected value

Распространенные применения:

  • Валидация и нормализация (как в @Trimmed).
  • Синхронизация с UserDefaults (@UserDefault).
  • Ограничение значений (@Clamped(min: 0, max: 100)).
  • Ленивая инициализация (встроенный @Lazy).
  • Thread-safe доступ к свойствам.

Итог: Property Wrapper — мощный инструмент для соблюдения принципа DRY, позволяющий выносить стандартную логику работы со свойствами в отдельные, тестируемые компоненты.

Ответ 18+ 🔞

А, property wrapper! Ну это ж просто пиздец какой-то удобный механизм, блядь! Сидишь такой, пишешь код, и каждый раз одно и то же — то строки триммировать, то в UserDefaults синхронизировать, то значения ограничивать. И вот ты уже, сука, двадцатый раз копипастишь эту хуйню, а потом смотришь — а что это за дичь? Property wrapper, ёпта!

Смотри, как это работает, блядь. Вместо того чтобы каждый раз в каждом свойстве писать одно и то же, ты эту логику в отдельную структуру засовываешь. А потом просто ставишь перед свойством собачку @ и имя этой структуры. И всё, блядь! Оно само работает!

Вот смотри, пример — обертка, которая строки от пробелов по краям чистит. Ну, classic, блядь:

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

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

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue // Используется setter
    }
}

Видишь? wrappedValue — это, блядь, святое. Без него нихуя не работает. Туда вся магия и зашита.

А потом используешь:

struct UserProfile {
    @Trimmed var username: String
    @Trimmed var email: String
}

var profile = UserProfile(username: "  john_doe  ", email: " email@example.com ")
print(profile.username) // "john_doe" (пробелы удалены, блядь!)
print(profile.email)    // "email@example.com" (пробел удален, красота!)

Во, блядь! Засунул пользователю в имя пробелы, а они взяли и исчезли! Магия, сука! А ты даже не парился.

А бывает ещё projectedValue — это такая, блядь, дополнительная фича. Через доллар $ к ней обращаешься. Ну, например, чтобы саму обёртку наружу вытащить или ещё какую хуйню.

Вот, смотри, обёртка, которая логирует, когда значение меняется:

@propertyWrapper
struct Logged<Value> {
    private var value: Value
    private let name: String

    var wrappedValue: Value {
        get { value }
        set {
            print("Свойство '(name)' изменено: (value) -> (newValue)") // Во, бля, сообщение!
            value = newValue
        }
    }

    var projectedValue: Self { self } // Проецируем саму обертку, ёпта

    init(wrappedValue: Value, _ name: String = "") {
        self.value = wrappedValue
        self.name = name
    }
}

struct Settings {
    @Logged("volume") var volume: Double = 0.5
}

var settings = Settings()
settings.volume = 0.7 // В консоли: "Свойство 'volume' изменено: 0.5 -> 0.7" — видишь, следит за тобой, сука!
let wrapper = settings.$volume // А вот и projected value через доллар, блядь!

И где это, блядь, применяется? Да везде, ёпта!

  • Валидация и нормализация — как наш @Trimmed, который строки подчищает.
  • Синхронизация с UserDefaults — сделал обёртку @UserDefault, и она сама туда-сюда пишет, не надо мозг ебать.
  • Ограничение значений@Clamped(min: 0, max: 100), чтобы, блядь, значение не уехало за пределы. Хуй на гору не полезет.
  • Ленивая инициализация — да там встроенный @Lazy уже есть, ленивая жопа.
  • Thread-safe доступ — чтобы с разных потоков не наебаться.

Короче, property wrapper — это, блядь, овердохуища полезная штука. Вместо того чтобы повторять один и тот же код как мудак, ты его в одном месте пишешь, а потом навешиваешь как декорацию. DRY принцип, блядь, в действии! Чисто, тестируемо, переиспользуемо. Ёперный театр, как же раньше без этого жили-то?