Computed properties in Swift: A basic feature for safer and cleaner code

Computed properties are one of the basic features of the Swift language. But despite their simplicity, they are a great tool to keep your code clean and free of bugs. I rely heavily on computed properties for any code I write.



Architecting SwiftUI apps with MVC and MVVM

GET THE FREE BOOK NOW

Contents

Stored properties can get out of synch and contain inconsistent data

First of all, let’s see why computed properties exist alongside stored properties.

As an example, let’s suppose we are writing a time tracker. Time tracking apps are quite popular nowadays, and you find them in many shapes and forms.

For starters, we need a type to represent the time entries in our app.

import Foundation

struct TimeEntry {
	let title: String
	let start: Date
	var end: Date
}

This is a pretty simple structure. We can get the values for the start and end stored properties when the user starts and stops a timer when working on some task.

Dates are not enough, though. A serious time-tracking app also needs to show the total duration of each time entry. For that, we can add a new property to our structure.

struct TimeEntry {
	let title: String
	let start: Date
	var end: Date
	var duration: DateComponents
}

When we create a new entry, we have to calculate the duration of the task and store it in the duration property. 

That’s not so hard. But that’s code we need to repeat every time we create a new entry. In a real app, it’s unlikely that that will happen in a single place.

That duplicates code, which makes it likelier to make mistakes somewhere and create entries where the duration is inconsistent with the start and end properties.

Making the value of a property dependent on other information

The obvious solution is to move the code that calculates the duration for an entry to a separate function. Any code that creates a new time entry can then rely on it, avoiding repetitions.

But let’s dig deeper.

There is a reason why these properties need to be consistent: they represent the same piece of information. The duration value is nothing else than a different way of looking at the data already stored in the other two properties.

Since we already have that information, it does not make sense to store it twice. It’s better to derive the duration of a time entry from that information.

struct TimeEntry {
	let title: String
	let start: Date
	var end: Date
	
	var duration: DateComponents {
		Calendar.current
			.dateComponents([.hour, .minute, .second], from: start, to: end)
	}
}

Now, duration is a computed property. Instead of storing its content separately, it derives it from the start and end properties. We can’t store inconsistent values anymore.

I didn’t use the return keyword in the code above because, since Swift 5.1, that’s not required in single expression functions.

Computed properties contain code, so they are, in practice, methods with no parameters. Then, why does Swift offer this extra feature?

There are different benefits of using this notation. The first one is that, semantically, the duration is an intrinsic property of a time entry. So, syntactically, it makes sense to represent it as a property.

Functions represent calculations. While this is one indeed, that’s not information we want to convey to the caller of our code.

But computed properties also have other benefits.

Assigning a new value to a computed property

Time tracking apps also allow the user to edit existing time entries to fix mistakes.

Making the user enter the start and end dates would provide a bad experience. It’s better to allow the user to enter the duration of an entry directly. Once we have that value, we can calculate the value of the end stored property. 

We need, again, some code that ties the properties together.

Currently, our duration computed property is read-only. But Swift also allows computed properties that accept new values.

struct TimeEntry {
	let title: String
	let start: Date
	var end: Date
	
	var duration: DateComponents {
		get {
			Calendar.current
				.dateComponents([.hour, .minute, .second], from: start, to: end)
		}
		set {
			end = Calendar.current
				.date(byAdding: newValue, to: start) ?? end
		}
	}
}

The get and set keywords define a getter and a setter for a computed property. The setter takes a new value and calculates the end date.

Again, we could have done the same with a method. But computed properties are better because, from the outside, they look like any other property. Semantically, the caller can simply assign a new duration to a time entry, which is more intuitive.

Moreover, a computed property with both a getter and a setter provides a single point of entry. If we used methods, we would have two provide two separate ones, which would make the interface of TimeEntry less intuitive.

Adding computed properties to existing types using extensions

Computed properties in Swift are not limited to concrete types like structures and classes. You and also add them to extensions.

With extensions, you can add functionality to a type without changing its implementation. This is particularly useful for types you don’t own and is at the core of protocol-oriented programming.

But extensions are limited and cannot add stored properties to a type (the reason is that extra stored properties would change the size of a type in memory, creating problems for the Swift compiler).

But computed properties are functions, so we can use them in extensions as well.

Many time tracking apps also allow the user to create invoices for clients based on the time spent on a project. For that, we need the multiply the hours spent on a project by an hourly rate.

The duration property of TimeEntry returns a DateComponents value, which is helpful when entering values but cumbersome for calculations.

We can fix it with a new computed property that returns the duration of an entry in hours.

extension TimeEntry {
	var hours: Double {
		DateInterval(start: start, end: end).duration * 3600
	}
}

I added the hours property to TimeEntry using an extension as an example. In our particular case, it makes no difference, and we could have added to the type instead. 

There are cases, though, where using extensions for your types makes sense since you can:

  • keep your code organized by functionality;
  • add convenience that does not belong to the type itself, e.g., data formatting for the UI of your app;
  • share types between apps without sharing code that is specific to a project.

Adding computed properties to types you don’t own

We can now create a new type representing a project, with a list of time entries and an hourly rate for the billing.

struct Project {
	let name: String
	let entries: [TimeEntry]
	let rate: Int
	
	var billableAmount: Int {
		let totalDuration = entries.reduce(0.0, { $0 + $1.hours })
		return Int(totalDuration * Double(rate))
	}
}

To calculate the totalDuration is used the reduce(_:_:) method, which belongs to functional programming. I prefer it because it allows me to perform the calculation on a single line. But you can use a Swift for loop instead if you prefer.

Calculating the billable amount for a project is not enough. We usually want to display it in a readable format in the user interface of our app and invoices. Money amounts are typically formatted using thousands and decimal separators, and currency symbols.

Currency formatting often appears in many places, so that’s code we want to reuse. 

But adding it to the Project structure would be wrong.

In the MVC pattern, data types should not contain any code related to the UI. So, to keep formatting code out of model types, we can add it to the Int type instead, using an extension.

extension Int {
	var currencyFormat: String {
		let formatter = NumberFormatter()
		formatter.numberStyle = .currency
		return formatter.string(from: NSNumber(value: self)) ?? ""
	}
}

Adding code to types you don’t own is not only useful to respect design patterns. You can also use it to make types you don’t own conform to some protocol.

Computed properties inside enumerations

There are times in which you need to make enumeration code reusable. Swift enumerations cannot have stored properties. But again, computed properties are functions, so we can use them also with enumerations.

Not all projects in a time tracking app are billed hourly. Some projects have a fixed fee. Personal or internal projects, instead, are not billable. 

This is a good use case for an enumeration.

struct Project {
	enum Billing {
		case nonBillable
		case hourly(rate: Int)
		case fixed(fee: Int)
	}
	
	let name: String
	let entries: [TimeEntry]
	let billing: Billing
	
	var billableAmount: Int {
		switch billing {
		case .nonBillable:
			return 0
		case let .hourly(rate):
			let totalDuration = entries.reduce(0.0, { $0 + $1.hours })
			return Int(totalDuration * Double(rate))
		case let .fixed(fee):
			return fee
		}
	}
}

Again, we want to show the different billing options clearly in our user interface. But a single number alone is confusing. The user might not understand that $0 means non-billable. Moreover, an amount of $500 might be a fixed fee for a small project, or the hourly rate of a well-paid freelancer.

(yes, that’s possible)

the upwork profile of a highly paid freelance software developer

We can format each case of the Billing enumeration inside a computed property.

extension Project.Billing {
	var formatted: String {
		switch self {
		case .nonBillable:
			return "Non billable"
		case let .hourly(rate):
			return "Hourly rate: " + rate.currencyFormat
		case let .fixed(fee):
			return "Fixed fee: " + fee.currencyFormat
		}
	}
}

Again, I used an extension instead of adding the computed property to Billing to keep formatting code separate from the type itself.

Conclusions

While computed properties are a form of syntactic sugar, they have several benefits.

  • You can make a property dependent on other information, avoiding inconsistent states.
  • You can hide functionality behind a simple interface. The caller can use a computed property as if it was a real one.
  • With getters and setters, you can keep related code in a single place.
  • Through extensions, you can add properties to any type. This is necessary to make a type you don’t own conform to a protocol.

Architecting SwiftUI apps with MVC and MVVM

It's easy to make an app by throwing some code together. But without best practices and robust architecture, you soon end up with unmanageable spaghetti code. In this guide I'll show you how to properly structure SwiftUI apps.

GET THE FREE BOOK NOW

2 thoughts on “Computed properties in Swift: A basic feature for safer and cleaner code”

  1. Hi Matteo. Just finished reading for the third time or so your computed properties blogpost. Excellent stuff, very useful.

    There’s something in this post that I don’t understand. The duration variable is declared twice in the TimeEntry struct. This is a typo, correct? Or is this a Swift technique that I’m not aware of?

    Reply

Leave a Comment