A computed property is a property that calculates and returns a value, rather than just store it. In this tutorial, we’ll discuss how computed properties work and how you can use them in your day-to-day iOS development.
Here’s what we’ll get into:
Ready? Let’s go.
Before we discuss computed properties, let’s do a quick recap of what properties are. Here’s a quick summary:
name
in user.name = "John"
is a stored property, and user
is an instance of a class.Let’s move on. A computed property calculates a value, rather than store it. Here’s an example:
struct Rectangle
{
var width:Double
var height:Double
var area:Double {
width * height
}
}
In the above Swift code, we’ve defined a struct called Rectangle
. It has two properties width
and height
of type Double
. The third property, called area
of type Double
, is a computed property. Here it is once more:
var area:Double {
width * height
}
The above area
property executes the code width * height
, and return its value, when it’s is called. Here, check this out:
let square = Rectangle(width: 12.0, height: 12.0)
print(square.area)
In the above code, we’ve defined a square with a width and height of 12 units. We then calculate the surface area of the square by referencing the area
property of square
, and print out its result. Internally, the code width * height
is called, i.e. 12.0 * 12.0 = 144.0
, and returned.
If we had defined the area
property as a function, it would have looked something like this:
func area() -> Double {
return width * height
}
See how that works? A computed property is kinda like a function, but different. Computed properties have a dense, concise syntax – and when used well, they make for more expressive and clearer code.
Note: In the above example, for area
, we’ve created a read-only computed property, which uses an implicit return. More on that, below!
OK, so far we’ve established that a computed property calculates a value. They execute some code when you call ’em, and they’re different from stored properties and functions. So far so good!
Computed properties can also provide a custom getter and setter.
Let’s take a closer look with an example:
struct User
{
private var firstName = ""
private var lastName = ""
var name:String {
get {
return firstName + " " + lastName
}
set(newValue) {
let split = newValue.components(separatedBy: " ")
firstName = split[0]
lastName = split[1]
print("firstName = \(firstName), lastName = \(lastName)")
}
}
}
var user = User()
user.name = "John Doe"
print(user.name)
In the above code, we’ve created a User
struct with two private properties firstName
and lastName
of type String
. The third property is computed, called name
of type String
.
The computed property name
has a custom getter and setter defined. Here’s a quick overview of the syntax we use for that:
var property:type {
get {
code
}
set(value) {
code
}
}
As part of the computed property, we can specify what happens when a property is read (getter) or changed (setter). Within the getter, you’re supposed to return a value. Within the setter, you’re supposed to store or change some value.
In the previous code sample, here’s what happens:
User
struct that has a private implementation for the firstName
and lastName
properties. We can’t change those from the outside, but only through the name
property.name
property, the code for get { ··· }
is executed. You get the value of the firstName
and lastName
strings with a space in between.name
, the code for set { ··· }
is executed. In the code, the provided string newValue
is split into components, and assigned to firstName
and lastName
.We can use the local newValue
constant as the value that’s provided, so we have access to both the new and the current values. This newValue
constant is implicitly available inside the setter, so you don’t have to declare it explitly. You can, however, provide your own constant name, if you want. Like this:
set {
// Use `newValue` in here (implicit)
}
set(newString) {
// Use `newString` in here (explicit)
}
Quick tip: For the sake of this example, we’re assuming that all person’s first and last names are separated with one space character. This isn’t true in the real world, of course – names come in all shapes and sizes. Yay!
We’ve looked at getters and setters for computed properties in the previous sections, but what about a property that only has a getter? That’s a read-only computed property; it can only be read, and not set.
Here, check this out:
struct Circle
{
var radius:Double
var circumference:Double {
2 * .pi * radius
}
}
In the above code, we’ve defined a struct Circle
that has a stored property radius
and a computed property circumference
, both of type Double
. We can use it like this:
let earth = Circle(radius: 6371)
print(earth.circumference) // Output: 40030.173592041145
That circumference
property is computed and read-only – it only has a getter. In fact, the above declaration of circumference
is exactly the same as this:
var circumference:Double {
get {
return 2 * .pi * radius
}
}
Comparing it against the previous code sample, you see two differences:
get { ··· }
part is removed. When left out, Swift assumes we’re declaring a read-only computed property.return
statement. Since Swift 5.1, single-line expressions can omit an explicit return
for the sake of clarity, brevity and expressiveness.Awesome! Let’s look at a few real-world scenarios for computed properties, next.
When you think about it, computed properties are just functions without parameters. Aren’t they? In essence, the whole of programming is just syntactic sugar and “structure” around 1’s and 0’s… Merely saying “Computed properties are just functions without ()
_”_ misses the whole point!
Let’s look at a few real-world use cases.
First, the read-only computed property is ideal for expressing simple calculated values concisely. Consider the circumference of a circle, that we’ve discussed before. You can, for example, provide a person’s formatted address based on individual values. Like this:
struct Person {
var street:String
var streetNumber:String
var city:String
var postcode:String
var address:String {
"\(street) \(streetNumber)\n\(postcode), \(city)"
}
}
let bob = Person(···)
print(bob.address) // Output: Infinite Lane 42 12A34B, Diamond City
More specifically, the above properties make sense from a stored database perspective. The address
property is essentially a view into the model data of Person
. You don’t have to store the complete address, just its individual components. You can them present them in any way you want.
Secondly, you can use computed property getters and setters to adjust other values than a property itself. Here, check out this example:
struct RentalCar
{
var costPerDay:Double
var costPerWeek:Double {
get {
costPerDay * 7.0
}
set {
costPerDay = newValue / 7.0
}
}
}
var toyota = RentalCar(costPerDay: 12.0)
toyota.costPerWeek = 100.0
print(toyota.costPerDay) // Output: 14.28
In the above code, we’re creating a struct RentalCar
that has a two properties:
costPerDay
of type Double
costPerWeek
of type Double
We could locally say that the cost per week of a rental is the cost per day times 7, and vice versa. In the above code, you see that the costPerDay
is the absolute determining factor for renting a car for any length of time.
costPerWeek
is set, we divide it by 7 and write it to costPerDay
.costPerWeek
is read (“get”), we multiply it by 7 to get the cost per week.In your development, you can now work with either day or week units, thanks to computed properties. Your code is, as far as this example goes, more concise and easier to read. The ratio between days and weeks is also an implementation detail of the RentalCar
struct itself, so your interfacing code doesn’t have to worry about mediating between the two. Awesome!
In this tutorial, we’ve discussed how to work with computed properties in Swift. We’ve looked at read-only properties, getters and setters, and practical examples in real-world iOS development.
Want to learn more? Check out these resources: