Property Wrappers in Swift

Property wrappers were introduced in Swift 5.1(WWDC19) and they play a big part in SwiftUI. Property wrappers provide a way to reduce the amount of duplicate code which is involved in writing computed properties or getters and setters for variables to validate or transform their values before storing them within a class and struct.

Let’s understand the problem property wrapper try to solve a little better.

Imagine we have a struct to represent DevTechie course app:

struct Course {
    var name: String = ""
    var author: String = ""
}
we have two properties for Course struct name and author . While storing values in these properties, we want to make sure that our course name and author is capitalized. So in order to put that validation in place we will make properties private and provide getter and setter for these private properties. For private variables we will rename them to _name and _author so all external references to those properties don’t change as shown below:

struct Course {
    private var _name: String = ""
    
    var name: String {
        get { _name }
        set { _name = newValue.capitalized }
    }
    
    private var _author: String = ""
    
    var author: String {
        get { _author }
        set { _author = newValue.capitalized }
    }
    
}
Now when course’s name and author properties are set, we capitalize them before storing into our private variables.

var course = Course()
course.name = "machine learning in iOS"
course.author = "devTechie"print(course.name, ", By - ", course.author)
Our output will show

Machine Learning In Ios , By -  Devtechie
Notice that the code to capitalize the value is repeated for each property. Imagine having 10 or 20 properties in a type, we will find ourselves repeating over and over.

Property wrapper to the rescue 😊

Property wrappers are declared using @propertyWrapper directive, decorated right before class or struct implementation. Property wrappers must include a property called wrappedValue which is a computed property and has getter and setter code.

@propertyWrapper
struct CapitalCase {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized() }
    }
    init(wrappedValue initialValue: String) {
        self.wrappedValue = initialValue
    }
}
Once our property wrapper is in place, we can decorate our course properties with this newly created property wrapper.

struct Course {
    @CapitalCase var name: String = ""
    @CapitalCase var author: String = ""
}
Our example usage remain same:

var course = Course()
course.name = "machine learning in iOS"
course.author = "devTechie"print(course.name, ", By - ", course.author)
Our output will also remain same:

Machine Learning In Ios , By -  Devtechie

Let’s focus on accepting multiple values for property wrappers.

Supporting multiple properties for Property Wrappers

In this section, we will focus on accepting and supporting multiple properties for property wrappers.

In this example, we will design a property wrapper which will allow us to add discount to DevTechie online video course sale. As we know that, discount value can’t be below 1%(no point of having discount 😂) and it can’t be above 100% so we will design property wrapper which will constrain discount to be between 1 and 100.

Our property wrapper will be called DevTechieDiscount (aptly named ☺️), it will have two variables to constrain discount to min and max values. We will also have init which will be initialized with min, max values of discount that can be applied on course.

Let’s look at our property wrapper definition:

@propertyWrapper
struct DevTechieDiscount {
    var min: Int
    var max: Int
    var value: Int
    
    init(wrappedValue: Int, min: Int, max: Int) {
        value = wrappedValue
        self.min = min
        self.max = max
    }
    
    var wrappedValue: Int {
        get { value }
        set {
            if newValue > max {
                value = max
            } else if newValue < min {
                value = min
            } else {
                value = newValue
            }
        }
    }
}
Notice wrappedValue definition for set , we are checking if value we are trying to set is more then the defined max then we set value to be the max same goes for min if newValue is smaller then min , we set value to be min and for all other cases where newValue falls between min and max we simply set value to be the newValue.

Let’s look at our Course struct:

struct Course {
    var name: String = ""
    var price: Int = 10
    @DevTechieDiscount(min: 1, max: 50) var discount: Int = 10
    
    func getFinalPrice() {
        print("Final price: ", price - price * discount / 100)
    }
}
Take a look at discount variable and how its decorated with DevTechieDiscount property wrapper which has min and maxdefined, meaning min discount that can be applied will be 1% and max discount that can be applied will be 50%.

We also have a function called getFinalPrice, which will print price after discount has been applied.

Lets create course object with values for name, price and discount.

var course = Course()
course.name = "SwiftUI in Depth"
course.price = 30
course.discount = 10course.getFinalPrice()
getFinalPrice will print following:

Final price:  27
So our discount is computed as 30–30*10/100.

Lets try to set discount value to more then the max allowed value.

var course1 = Course()
course1.name = "SwiftUI 3"
course1.price = 50
course1.discount = 100course1.getFinalPrice()
This will print following:

Final price:  25
Discount applied is still 50%, which is the max defined by DevTechieDiscount property wrapper.

Generic Property Wrapper

As we have seen so far, property wrappers are special cases of types(struct or class). This mean we can convert our property wrapper to generic so it can be used to any types that meets criteria defined by the property wrapper.

Let’s take a look at example, we will change DevTechieDiscount ‘s name to MinMax .

@propertyWrapper
struct MinMax {
    var min: Int
    var max: Int
    var value: Int
    
    init(wrappedValue: Int, min: Int, max: Int) {
        value = wrappedValue
        self.min = min
        self.max = max
    }
    
    var wrappedValue: Int {
        get { value }
        set {
            if newValue > max {
                value = max
            } else if newValue < min {
                value = min
            } else {
                value = newValue
            }
        }
    }
}
This is great for finding out min max but at this point our implementation is limited to Int type only. We would like to change that and make it generic so as long as value type conforms to Comparable protocol, we can find min and max for that type.

@propertyWrapper
struct MinMax<T: Comparable> {
    var min: T
    var max: T
    var value: T
    
    init(wrappedValue: T, min: T, max: T) {
        value = wrappedValue
        self.min = min
        self.max = max
    }
    
    var wrappedValue: T {
        get { value }
        set {
            if newValue > max {
                value = max
            } else if newValue < min {
                value = min
            } else {
                value = newValue
            }
        }
    }
}
With this change let’s apply this to our Course type to try it out:

struct Course {
    var name: String = ""
    var price: Int = 10
    @MinMax(min: 1, max: 50) var discount: Int = 10
    
    func getFinalPrice() {
        print("Final price: ", price - price * discount / 100)
    }
}
We will create course object and print discounted price:

var course = Course()
course.name = "SwiftUI in Depth"
course.price = 30
course.discount = 10course.getFinalPrice()
getFinalPrice will print following:

Final price:  27
Let’s change Course ‘s discount to Double :

struct Course {
    var name: String = ""
    var price: Double = 10
    @MinMax(min: 10.5, max: 75.5) var discount: Double = 10
    
    func getFinalPrice() {
        print("Final price: ", price - price * discount / 100)
    }
}
We will create course object again:

var course = Course()
course.name = "SwiftUI in Depth"
course.price = 30
course.discount = 10course.getFinalPrice()
This time the output we will get will be

Final price:  26.85
Notice that final price has changed because discount set in object is 10% but min supported discount will be 10.5% so discount will change to 10.5 and final price will be computed as 30–30 * 10.5 / 100 = 26.85

We can see that we didn’t have to re-create property wrapper for Double values. This implementation will work for strings as well 🙃.

Data Validation using Property Wrapper

For our next example, we will move on to sign in page for DevTechie Courses app. For sign in scenario our model will have to verify email and phone number for user which are perfect candidates for property wrappers.

First we will create a RegExValidator which will validate both email and phone number for us(or any other regex worthy validation 🤩)

@propertyWrapper
struct RegExValidator<T: StringProtocol> {
    var value: T?
    var regEx: String
    
    init(regEx: String, wrappedValue: T) {
        value = wrappedValue
        self.regEx = regEx
    }
    
    var wrappedValue: T? {
        get {
            validate(value: value) ? value : nil
        }
        set {
            value = newValue
        }
    }
    
    private func validate(value: T?) -> Bool {
        guard let value = value else { return false }
        
        let predicate = NSPredicate(format:"SELF MATCHES %@", regEx)
        return predicate.evaluate(with: value)
    }
}
DevTechie Login model will look like this, notice our validator and regex patterns.

struct DevTechieLogin {
    var name: String
    @RegExValidator(regEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}", wrappedValue: "")
    var email: String?
    
    @RegExValidator(regEx: "(\\+\\d{1,2}\\s)?\\(?\\d{3}\\)?[\\s.-]\\d{3}[\\s.-]\\d{4}", wrappedValue: "")
    var phone: String?
}
Let’s put test code to check both valid and invalid cases:

Valid case:

var signIn = DevTechieLogin(name: "DevTechie")
signIn.email = "example@example.com"
signIn.phone = "999-999-9999"print(signIn.name, signIn.email ?? "--Invalid--", signIn.phone ?? "--Invalid--")
Output:

DevTechie example@example.com 999-999-9999
Invalid case:

var signIn = DevTechieLogin(name: "DevTechie")
signIn.email = "exampleexample.com"
signIn.phone = "9999999999"print(signIn.name, signIn.email ?? "--Invalid--", signIn.phone ?? "--Invalid--")
Output:

DevTechie --Invalid-- --Invalid--
Limitations of property wrappers

Property wrappers are awesome and when used at the right place can make code much easier to maintain and efficinet but like everything else, there are few things even property wrapper can’t do:

  • Properties can’t have multiple property wrapper applied to them
  • Property wrappers can’t throw errors so error handling is not possible.