Property Wrappers in Swift

Evangelist Apps
4 min readFeb 24, 2023

--

What is `Property Wrapper`?

A property wrapper is a feature introduced in Swift 5.1. From the Swift Programming language guide, A Property Wrapper is defined as below:

A property wrapper adds a layer of separation between the code that manages how a property is stored and the code that defines a property. -swift.org

As the name implies, a property wrapper is a type that wraps a property into the new logic. In simpler terms, a Swift Property Wrappers is a feature that allows to define a custom type to implement behaviour from `getter` and `setter` method and reuse it.

It is used to decorate properties in your class, struct or enum. It helps to reduce boilerplate code and improves readability.

When should you use Property Wrapper?

Let’s take the below example of struct Student that returns and prints out the name in all caps.

struct Student {
var firstName: String
var lastName: String
}

Instead of applying the uppercased() wherever we needs to convert it. There are a couple of effective ways we can achieve it in Swift. They are

  1. Property Observers
  2. Computed properties

Using Property Observers to capitalize the names

struct Student {
var firstName: String {
didSet {
firstName = firstName.uppercased()
}
}
var lastName: String {
didSet {
lastName = lastName.uppercased()
}
}
init(firstName: String, lastName: String) {
self.firstName = firstName.uppercased()
self.lastName = lastName.uppercased()
}
}

Using Computed Properties to capitalize the names

struct Student {
private var _firstName: String = ""
var firstName: String {
set {
_firstName = newValue.uppercased()
}
get {
return _firstName
}
}
private var _lastName: String = ""
var lastName: String {
set {
_lastName = newValue.uppercased()
}
get {
return _lastName
}
}
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}

In both of these approaches we have some drawbacks i.e if there are `N` number of variables in the struct to be capitalized, this would lead to applying the logic to all the variables resulting in a lot of redundant code. This is where Property Wrapper comes for a rescue and a perfect place to avoid the redundant/ boilerplate code.

How to create a custom property wrapper?

To define a property wrapper, we have to decorate the class, struct or enum type with the @PropertyWrapper keyword and then have to implement a property called the wrappedValue in it.

Common lets create a new type called AllCaps property observer to always return the name in capitalized.

Property Observers Approach:

@propertyWrapper
struct AllCaps {
var wrappedValue: String {
didSet {
wrappedValue = wrappedValue.uppercased()
}
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue.uppercased()
}
}

We added the @PropertyObserver in frontof the struct AllCaps and added a wrappedValue property in it. On the wrappedValue, we added the property observer to apply the capitalized logic.

On the property declaration, we have to decorate the property with the @AllCaps keyword which is a property observer. We just need to add the @AllCaps keyword to the properties that return the property in capitalized letters.

@AllCaps var name: String = "Steve Jobs"

Now when we try to access the property it will always return the string in capitalized letters.

In this property observer approach, we have to apply the logic in init because didSet observers are not called when the property is first initialized so we have to add the logic in init.

So we are going to use an alternate approach computed property in the property wrapper.

Computed Property Approach:

In this approach, we are applying the logic only in the getter instead of applying it twice like the property observer approach.

Example 1:

@propertyWrapper 
struct AllCaps {
private var name: String
var wrappedValue: String {
set {
name = newValue
}
get {
return name.uppercased()
}
}
init(wrappedValue: String) {
self.name = wrappedValue
}
}
// usagestruct Student {
@AllCaps var firstName: String
@AllCaps var lastName: String
}
var student = Student(firstName: "Steve", lastName: "Jobs")
print("Hello \(student.firstName) \(student.lastName)")
// output:Hello STEVE JOBS

Example 2:

Let’s add another property in student called score which is always in the range between 0 to 100. So we are going to create another property wrapper called InRange which will allow the property to be in between the range 0 to 100.

@propertyWrapper struct InRange {
private var mark: Int
var wrappedValue: Int {
set {
mark = newValue
}
get {
return max(0, min(mark, 100))
}
}
init(wrappedValue: Int) {
mark = wrappedValue
}
}
struct Student {
@InRange var mark: Int
}
let student1 = Student(mark: 75)
print(student1.mark)
// output:
75
let student2 = Student(mark: 110)
print(student2.mark)
// output:
100
let student3 = Student(mark: -20)
print(student3.mark)
// output:
0

Here we added the logic in the getter that when the mark is less than 0 means we are setting it to 0 and when the mark is greater than 100 means we will set it to 100.

There are numerous places we can use a property wrapper to avoid the duplicate like User Defaults, Threading, Orientation Change, Lazy variables etc.,

In the next article, we will discuss about another special property in the Property Wrapper called Projected Value.

Thanks for reading!

Please follow us on Twitter and LinkedIn for more updates.

By Vinodh Kumar, Senior iOS Developer at Evangelist Apps

#SwiftUI #App Storage #Swift.org #evangelistapps

--

--

Evangelist Apps

Evangelist Software is UK based mobile apps development agency that specializes in developing and testing iOS apps.