Swift Programming

Quick Introduction to Property Wrappers in Swift

Learn what property wrappers are and how to use them

What is a property wrapper in Swift?

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

-swift.org

In all honesty when I read this bit from Swift.org’s documentation I did not quite understand what property wrappers are. So again what is a property wrapper in Swift?

Before I explain Swift Property Wrappers let’s briefly recap two other Swift features:

  1. lazy loading properties
  2. optionals

I assume you are familiar with these already. Additionally you must be familiar with the basics of iOS and Swift development for this post.

Lazy loading properties:

lazy var myVar = MyClass()

Lazy loading properties allows us to load the property upon the first time we access it. We use it to defer the loading of the property to a point when we need it. In some cases we’ll never need it and thus the property never gets loaded.

Optionals:

var myOptionalVar: MyClass? = nil

With optionals we are able to declare that the value of the property may be absent (that is set to nil) at the present time and that we must check that the value is set before performing the operation.

An alternative way to declare your variable as an optional is:

var myOptionalVar: Optional<MyClass> = nil

What have these two features in common? Both of these modify the behaviour of the property. Both of these wrap the property to provide additional reusable functionality. What if these features did not exist?

Without lazy:

func getMyClass() -> MyClass {
if self.myVar == nil {
self.myVar = MyClass()
}
return self.myVar
}

For every property you would like lazy loaded you’d have to make sure the value is set. If its not set then set it. Then return the value. Remember to only to access myVar through the getMyClass function.

Without optionals you’d have to implicitly know that a property can be nil or not. A mistake in programming would only be revealed at runtime. Currently the compiler is able to check you adhere to the contract at compile time. To make sure you are adhering to the contract without Swift optionals you’d have to test your code everywhere you are optionally setting the value of a property.

So having features that wrap our property and provide us with additional reusable functionality is great. It saves us from a lot of extra work. However how can we write our own custom logic to modify the behaviour of a property in Swift?

Swift Property Wrappers

In Swift (5.1 or newer) we can write our own custom property wrappers to modify the behaviour of our properties with custom reusable logic. We do so by creating a class or struct preceeded by @propertyWrapper. A property wrapper must contain a property within named wrappedValue.

Let’s look at an example. Let’s say you want to present a screen to guide the user of how to use your app upon launch. One approach is to persist a property in UserDefaults to flag whether or not to show the guide upon app launch.

let showGuide = UserDefaults.standard.object(forKey: "show_guide") as? Bool ?? true
if showGuide {
// show guide
}

When the user taps on the “Do not show again” button on the guide we want to set the property to false:

UserDefaults.standard.set(false, forKey: "show_guide")

Let’s say your app has a main view controller which is the entry point for the user at all times. We’ll present the guide on top of the main view controller:

class MainViewController: UIViewController, GuideViewControllerDelegate {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let showGuide: Bool = UserDefaults.standard.object(forKey: "show_guide") as? Bool ?? true
if showGuide {
self.showGuide()
}
}
// GuideViewControllerDelegate func userTappedDoNotShowGuideAgain() {
UserDefaults.standard.set(false, forKey: "show_guide")
}
}

But what if you could set a single instance property? For example:

class MainViewController: UIViewController, GuideViewControllerDelegate {    var showGuide: Bool
...
}

You could use computed properties and have a getter and setter for convenience:

class MainViewController: UIViewController, GuideViewControllerDelegate {    var showGuide: Bool { 
get {
return UserDefaults.standard.object(forKey: "show_guide") as? Bool ?? true
}
set {
UserDefaults.standard.set(false, forKey: "show_guide")
}
}
...
}

However you would have to the same boilerplate code for each of properties that is stored in UserDefaults.

Let’s use Swift Property Wrappers to reduce the noise and create a reusable solution to back up our properties in UserDefaults:

@propertyWrapper
struct
UserDefault<T> {
var key: String
var defaultValue: T
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}

And now you can change your main view controller to:

class MainViewController: UIViewController, GuideViewControllerDelegate {
@UserDefault(key: "show_guide", defaultValue: true) var showGuide: Bool
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.showGuide {
self.showGuide()
}
}
// GuideViewControllerDelegate func userTappedDoNotShowGuideAgain() {
self.showGuide = false
}
}

Using a property wrapper we have:

  • made the setting and accessing UserDefaults boilerplate code reusable
  • made the code more readable

Note the UserDefault property wrapper example shared in this post has already been implemented within the SwiftUI framework as @AppStorage and is available in iOS 14 or newer.

Summary

In this post we:

  • how lazy and optionals Swift features modify the behaviour of properties
  • how to implement our own custom property wrapper to modify our desired properties behaviour

Stay tuned for more on iOS development! Follow me on Twitter or Medium.

Senior iOS Engineer @ Onfido. Writing weekly blogs on iOS and programming. Follow me to stay tuned!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store