Skip to main content

Swift Computed Properties

Introduction

In Swift, properties are a fundamental concept that allows you to associate values with a particular class, structure, or enumeration. While stored properties save a value as part of an instance, computed properties calculate a value on the fly. Think of computed properties as methods that look like properties—they run code to determine their value rather than simply retrieving a stored value.

Computed properties are particularly useful when a value depends on other properties or when you want to provide a custom getter and setter for a value without directly exposing the underlying data.

Understanding Computed Properties

The Basics

A computed property doesn't actually store a value. Instead, it provides a getter (and optionally a setter) that retrieves and sets other properties indirectly. Let's start with a simple example:

swift
struct Circle {
var radius: Double

// Computed property
var area: Double {
return Double.pi * radius * radius
}
}

// Create a circle instance
let circle = Circle(radius: 5.0)

// Access the computed property
print("Circle area: \(circle.area)")
// Output: Circle area: 78.53981633974483

In this example, area is a computed property that calculates the area of the circle based on its radius. The area isn't stored anywhere—it's calculated each time you access the property.

Syntax of Computed Properties

The basic syntax for a computed property is:

swift
var propertyName: Type {
get {
// Code to calculate and return the value
}
set(newValue) {
// Code to set other values based on the new value
}
}

The get block is mandatory, while the set block is optional. If you only provide a getter, you can simplify the syntax:

swift
var propertyName: Type {
// Code to calculate and return the value
}

Read-Only Computed Properties

When you only need to retrieve a calculated value without setting it, you can create a read-only computed property:

swift
struct Rectangle {
var width: Double
var height: Double

// Read-only computed property
var area: Double {
return width * height
}

var perimeter: Double {
return 2 * (width + height)
}
}

let rectangle = Rectangle(width: 10, height: 5)
print("Rectangle area: \(rectangle.area)")
// Output: Rectangle area: 50.0
print("Rectangle perimeter: \(rectangle.perimeter)")
// Output: Rectangle perimeter: 30.0

In this example, both area and perimeter are read-only computed properties that calculate their values based on the rectangle's width and height.

Read-Write Computed Properties

To create a property that can be both read and written to, you need to implement both a getter and a setter:

swift
struct Temperature {
// Stored property
var celsius: Double

// Read-write computed property
var fahrenheit: Double {
get {
return celsius * 9/5 + 32
}
set {
celsius = (newValue - 32) * 5/9
}
}
}

var temp = Temperature(celsius: 25.0)
print("Celsius: \(temp.celsius)°C")
// Output: Celsius: 25.0°C
print("Fahrenheit: \(temp.fahrenheit)°F")
// Output: Fahrenheit: 77.0°F

// Set the fahrenheit value
temp.fahrenheit = 86.0
print("Updated Celsius: \(temp.celsius)°C")
// Output: Updated Celsius: 30.0°C

In this example, fahrenheit is a read-write computed property. When we get the value, it converts from Celsius to Fahrenheit. When we set the value, it updates the celsius property by converting from Fahrenheit.

Notice how the setter receives a default parameter called newValue. You can also specify a custom parameter name:

swift
var fahrenheit: Double {
get {
return celsius * 9/5 + 32
}
set(newFahrenheit) {
celsius = (newFahrenheit - 32) * 5/9
}
}

Property Observers with Computed Properties

You can't directly add property observers to computed properties because you can already include any required logic in the getter and setter. However, you can observe changes to the stored properties that your computed property depends on:

swift
struct Progress {
var task: String
var amount: Double {
didSet {
amount = min(max(amount, 0), 1)
print("Progress for \(task) updated to \(Int(amount * 100))%")
}
}

var formattedPercentage: String {
return "\(Int(amount * 100))%"
}
}

var progress = Progress(task: "Loading data", amount: 0.0)
progress.amount = 0.5
// Output: Progress for Loading data updated to 50%
print(progress.formattedPercentage)
// Output: 50%

Here, formattedPercentage is a read-only computed property that formats the progress percentage, while amount is a stored property with an observer.

Practical Applications

Example 1: Personal Finance

Let's create a simple budget tracker using computed properties:

swift
struct Budget {
var income: Double
var expenses: [String: Double]

var totalExpenses: Double {
return expenses.values.reduce(0, +)
}

var balance: Double {
return income - totalExpenses
}

var summary: String {
return """
Income: $\(String(format: "%.2f", income))
Expenses: $\(String(format: "%.2f", totalExpenses))
Balance: $\(String(format: "%.2f", balance))
"""
}
}

var myBudget = Budget(income: 5000, expenses: [
"Rent": 1500,
"Food": 600,
"Utilities": 200,
"Entertainment": 300
])

print(myBudget.summary)
// Output:
// Income: $5000.00
// Expenses: $2600.00
// Balance: $2400.00

// Adding a new expense
myBudget.expenses["Transportation"] = 150
print("Updated balance: $\(String(format: "%.2f", myBudget.balance))")
// Output: Updated balance: $2250.00

This example demonstrates how computed properties can provide useful derived values based on the underlying data.

Example 2: User Profile

Computed properties can help manage user information:

swift
struct User {
var firstName: String
var lastName: String

var fullName: String {
return "\(firstName) \(lastName)"
}

var initials: String {
let firstInitial = firstName.first?.uppercased() ?? ""
let lastInitial = lastName.first?.uppercased() ?? ""
return "\(firstInitial)\(lastInitial)"
}

// A computed property with setter that updates the first and last name
var displayName: String {
get {
return fullName
}
set {
let components = newValue.components(separatedBy: " ")
if components.count > 0 {
firstName = components[0]
}
if components.count > 1 {
lastName = components.dropFirst().joined(separator: " ")
}
}
}
}

var user = User(firstName: "John", lastName: "Doe")
print("Full name: \(user.fullName)")
// Output: Full name: John Doe
print("Initials: \(user.initials)")
// Output: Initials: JD

// Change the display name
user.displayName = "Jane Smith"
print("Updated first name: \(user.firstName)")
// Output: Updated first name: Jane
print("Updated last name: \(user.lastName)")
// Output: Updated last name: Smith

This example demonstrates how computed properties can transform data and even update multiple stored properties when set.

Best Practices

  1. Use computed properties when the value depends on other properties. This ensures the value is always up-to-date.

  2. Keep computation simple. Since computed properties are evaluated every time they're accessed, complex calculations can affect performance.

  3. Consider caching expensive computations if they're accessed frequently but change infrequently.

  4. Use read-only computed properties when you only need to derive a value from other properties.

  5. Remember that computed properties cannot be constants (let). They must be declared with var.

Common Pitfalls

  1. Infinite recursion: Be careful not to create cycles where a computed property accesses itself.

    swift
    // Problematic code - causes infinite recursion
    var badProperty: String {
    return badProperty + "!" // Calls itself endlessly!
    }
  2. Side effects: Avoid side effects in getters, as they may be called more often than you expect.

  3. Performance concerns: Remember that computed properties recalculate each time they're accessed. For complex calculations, consider using stored properties with property observers instead.

Summary

Computed properties are a powerful feature in Swift that allow you to:

  • Calculate values dynamically based on other properties
  • Provide read-only or read-write access to derived values
  • Create clean and intuitive interfaces for your structures and classes
  • Encapsulate transformation logic within the property itself

They are particularly useful when a value is derived from other properties and needs to always reflect the current state of those properties.

Exercises

  1. Create a Person structure with birthYear as a stored property and age as a computed property that calculates the person's current age.

  2. Extend the Rectangle structure to include computed properties for diagonal (the length of the diagonal) and aspectRatio (width divided by height).

  3. Create a BankAccount structure with stored properties for balance and interestRate, and a computed property that calculates the interest earned over a specified number of months.

  4. Create a ShoppingCart structure that contains an array of Item objects, with computed properties for totalPrice, itemCount, and averageItemPrice.

  5. Build a Color structure with RGB values as stored properties and computed properties for HSL (Hue, Saturation, Lightness) values.

Additional Resources

Happy coding with Swift computed properties!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)