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.
Sharing is caring!