Property wrappers in Swift
It’s been almost a year since Apple introduced a new feature called property wrapper in Swift 5.1 in 2019. You probably heard it but you have never used it or maybe you have played around with it. Anyways since WWDC is around the corner and we are all waiting for new cool stuffs, I thought I would pick one feature from Swift that was released last year and try to explain. In this article I try to go over what is property wrapper and how we implement it to write a better code.
Property wrappers are basically a custom type that provides controlled access to a property. What that mean is, it encapsulates reading and writing to the property. This can be implemented using a class, struct or enum. What makes this concept interesting is that, it’s easy to implement and it can be reused everywhere, which reduce the redundant code we have to write every time. Isn’t that interesting? 🧐. I found it really interesting as I always try to avoid redundant code. To better understand the concept, Let’s go through it together, shall we? 😃
Alright, let’s pick a simple scenario that is easy to explain and doesn’t take much time. Say we want to display a user’s name first letter always capitalised. We could do this in many ways or even much simpler. However for the sake of example let's implement it using property wrapper. To do this:-
- First we need to declare a new type, for our example I have declared a new struct called Capitalized.
struct Captialized { }
- And then we need to add @propertyWrapper attribute to the newly created type. This has to be added on top of the new struct we created.
@propertyWrapper
struct Capitalized {
}
- We also have implement the required computed property. Here we use get to access the value of the property, which will be a capitalised string and whenever we want to assign a value to it we use set. Here is what this looks like in code:
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get {
return text.capitalized
}
set {
text = newValue
}
}
init(wrappedValue: String) {
self.text = wrappedValue
}
}
- Now that we have defined our own property wrapper, we can finally apply to properties in our classes or structs, to do this we need to define our property with an attribute name @Capitalized added right before the property. To demonstrate this let’s create a struct called User and define couple of properties, let's call them firstName and lastName.
struct User {
@Capitalized var firstName: String
@Capitalized var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
That’s all we have to do to implement a property wrapper for this simple scenario. Straightforward, right? Now when we access firstName and lastName, the first letter will be capitalised.
let user = User(firstName: "ezaden", lastName: "seraj")
print(user.firstName) //prints Ezaden
There are few things to note when we need to implement property wrappers.
- Property wrappers can NOT be applied to a constant property or a property defined as let. We can only apply to a variable. Say if we try to add the
@Capitalized
attribute right before a constant property, Xcode will yell at us. It won’t allow us. - Similarly, property wrappers can’t be applied to lazy variables, weak and unowned.
- Also keep in mindm we can’t declare a property with wrapper in extensions and protocols.
- Another big drawback for this cool feature is that it requires Swift 5.1 and it doesn’t support below iOS 13.
Thank you for reading this article 🙂, if you have any feedback on this article please don’t hesitate to contact me. And If you find it interesting please share it with out others.
Complete code:
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get {
return text.capitalized
}
set {
text = newValue
}
}
init(wrappedValue: String) {
self.text = wrappedValue
}
}
struct User {
@Capitalized var firstName: String
@Capitalized var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
let user = User(firstName: "ezaden", lastName: "seraj")
print(user.firstName)