What are property wrappers in Swift? How to use @propertyWrapper?
Property wrappers in Swift are a mechanism for adding extra logic when setting and getting properties. Property wrappers can reuse property management logic and reduce code duplication.
Basic Property Wrapper:
swift@propertyWrapper struct TwelveOrLess { private var number: Int init() { self.number = 0 } var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } } struct SmallRectangle { @TwelveOrLess var height: Int @TwelveOrLess var width: Int } var rectangle = SmallRectangle() rectangle.height = 10 rectangle.width = 20 print(rectangle.width) // 12
Property Wrapper with Parameters:
swift@propertyWrapper struct SmallNumber { private var maximum: Int private var number: Int init(wrappedValue: Int, maximum: Int) { self.maximum = maximum self.number = wrappedValue } var wrappedValue: Int { get { return number } set { number = min(newValue, maximum) } } } struct ZeroRectangle { @SmallNumber(wrappedValue: 0, maximum: 5) var height: Int @SmallNumber(wrappedValue: 0, maximum: 10) var width: Int }
Projected Value:
swift@propertyWrapper struct SmallNumber { private var number: Int init(wrappedValue: Int) { self.number = wrappedValue } var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } var projectedValue: Bool { return number > 12 } } struct SomeStructure { @SmallNumber var someNumber: Int } var someStructure = SomeStructure() someStructure.someNumber = 4 print(someStructure.$someNumber) // false someStructure.someNumber = 14 print(someStructure.$someNumber) // true
Common Property Wrapper Use Cases:
-
Thread Safety:
swift@propertyWrapper struct ThreadSafe { private var value: T private let lock = NSLock() init(wrappedValue: T) { self.value = wrappedValue } var wrappedValue: T { get { lock.lock() defer { lock.unlock() } return value } set { lock.lock() defer { lock.unlock() } value = newValue } } } -
Lazy Loading:
swift@propertyWrapper struct Lazy { private var value: T? var wrappedValue: T { mutating get { if let value = value { return value } let initialValue = wrappedValueFactory() value = initialValue return initialValue } } let wrappedValueFactory: () -> T init(wrappedValue: @autoclosure @escaping () -> T) { self.wrappedValueFactory = wrappedValue } } -
User Defaults:
swift@propertyWrapper struct UserDefault { let key: String let defaultValue: T var wrappedValue: T { get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } }
Best Practices for Property Wrappers:
- Use property wrappers to encapsulate repetitive property logic
- Use projected values to provide additional functionality
- Provide reasonable default values for property wrappers
- Be aware of the performance impact of property wrappers
- Use property wrappers where appropriate