PropertyWrapper in Swift

👍
· Mar 10, 2023 · 3 mins read
PropertyWrapper in Swift

Recently, I had some queries about property-wrapper in Swift. While I recommended them to had a read of this article, I also prepared a swift playground, which I would like to share here.

Background

What is a Property

In Swift, Properties associate values with a particular class, structure, or enumeration.

With backing store

struct Struct {  
    let property1: Int  
    var property2: Int  
    lazy var property2: Int = { 0 }()  
}

Without backing store

struct Struct {  
    var property2: Int {  
        0  
    }  
}

Having observer

struct Struct {  
    var value: String {  
        willSet {  
        }  
        didSet {  
        }  
    }  
}

Having accessor

struct Struct {  
    var value: String {  
        get {  
            return "value"  
        }  
        set {  
        }  
    }  
}

Property wrapper

ProperWrapper is a structure that encapsulates accessors of the property and adds additional behaviour to it. It promotes code reuse of the accessors.

Simplest form

@propertyWrapper  
struct BasicWrapper {  
    let wrappedValue: String  
}  
  
struct DemoStruct {  
    @BasicWrapper var value = "demo"  
}

Adding logic into accessors of `wrappedValue`

@propertyWrapper  
struct Clamping {  
    let range: ClosedRange<Int>  
    var value: Int  
  
    init(wrappedValue: Int, range: ClosedRange<Int>) {  
        self.range = range  
        self.value = wrappedValue  
    }  
  
    var wrappedValue: Int {  
        get {  
            min(max(range.lowerBound, value), range.upperBound)  
        }  
        set {  
            value = newValue  
        }  
    }  
}  
  
struct DemoStruct {  
    @Clamping(range: 0...100) var score = 0  
}

Another practical example

@propertyWrapper   
struct UserDefault<T> {  
    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)  
        }  
    }  
}  
  
enum GlobalSettings {  
    @UserDefault(key: "FOO\_FEATURE\_ENABLED", defaultValue: false)  
    static var isFooFeatureEnabled: Bool  
  
    @UserDefault(key: "BAR\_FEATURE\_ENABLED", defaultValue: false)  
    static var isBarFeatureEnabled: Bool  
}

Projected Value

Swift property wrapper has an special mechanism to map the wrapped value to another value and $ prefix syntax is provided to access this projected value. These are widely used in SwiftUI.

@propertyWrapper  
struct Clamping {  
    let range: ClosedRange<Int>  
    var value: Int  
  
    init(wrappedValue: Int, range: ClosedRange<Int>) {  
        self.range = range  
        self.value = wrappedValue  
    }  
  
    var wrappedValue: Int {  
        get {  
            min(max(range.lowerBound, value), range.upperBound)  
        }  
        set {  
            value = newValue  
        }  
    }  
  
    var projectedValue: Bool {  
        value > range.upperBound \* 6 / 10  
    }  
}  
  
struct DemoStruct {  
    @Clamping(range: 0...150) var mathScore = 80  
    @Clamping(range: 0...100) var historyScore = 80  
}  
  
print(DemoStruct().mathScore)  
print(DemoStruct().$mathScore)  
  
print(DemoStruct().historyScore)  
print(DemoStruct().$historyScore)

Usage in SwiftUI

SwiftUI heavily relies on property wrappers for certain functionalities. @State, @Binding, @ObservedObject are all property wrappers. The wrappers provide value storage for the wrapped properties and their projectValues are Binding of these value, which is why we can use $ prefix syntax to modify values of the properties.

Please also note, if you want the property wrappers have the power to update SwiftUI views like @State does, let them conform to `DynamicProperty`.

Swift Playground code repo

Please checkout the playground code here.

Comments

Sharing is caring!