Skip to Content
Course content

17: Computed Properties and Property Observers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this all the time when I'm reviewing PRs from developers moving into Swift: they try to attach a didSet observer to a computed property. They expect the observer to fire whenever the computed value "changes."

// This will NOT compile
var totalScore: Int {
    return kills * 100 + assists * 50
}
didSet {
    print("Score updated!") // Compiler Error: Property observers cannot be added to computed properties
}

Here is why that's a fundamental misunderstanding: a computed property doesn't have a value to change. It's actually just a pair of functions (a getter and a setter) disguised as a variable. When you access totalScore, Swift isn't looking up a value in memory; it's running a calculation. Since there is no actual storage being modified, there is nothing for an observer to "watch."

Computed Properties are Functions in Disguise

Think of a computed property as a way to provide a clean interface for a value that is derived from other data. I use these constantly to avoid "state synchronization" bugs—where you have two variables that should always be in sync, but one gets updated while the other is forgotten.

Let's look at a Rectangle. Instead of storing the area and updating it every time the width or height changes, we compute it on the fly:

struct Rectangle {
    var width: Double
    var height: Double
    
    var area: Double {
        return width * height
    }
}

In this case, area is a read-only computed property. But what if we want to be able to change the area? We can provide a set block. Inside the setter, Swift provides a default constant called newValue, which is the value the user is trying to assign to the property.

struct Rectangle {
    var width: Double
    var height: Double
    
    var area: Double {
        get {
            return width * height
        }
        set {
            // We'll keep the width constant and adjust the height to fit the new area
            height = newValue / width
        }
    }
}

I'll give you a pro tip here: use computed properties whenever a value can be derived from existing state. If you can calculate it, don't store it. It saves memory and prevents your data from getting out of sync.

Property Observers Watch the Storage, Not the Calculation

Now, if we actually want to perform an action when a value changes—like updating a UI label or logging a change to a database—we use Property Observers: willSet and didSet. Unlike computed properties, these must be attached to stored properties.

The observer doesn't replace the setter; it augments it. It's like a hook that runs right before or right after the value in memory is swapped.

class UserProfile {
    var username: String {
        willSet {
            print("About to change name to \(newValue)")
        }
        didSet {
            print("Changed name from \(oldValue) to \(username)")
        }
    }
    
    init(username: String) {
        self.username = username
    }
}

Note the difference in naming: willSet gives you newValue (what's coming), and didSet gives you oldValue (what just left). I find didSet to be the workhorse here; it's incredibly useful for triggering a "refresh" logic whenever a specific piece of state changes.

Just be careful: if you accidentally update the stored property inside its own didSet, you'll create an infinite loop and crash your app. Swift has some protections against this in newer versions, but it's still a logic trap to avoid.




📋 Practical Task

Exercise: Implementing a Game Character Stat Tracker

You are building a character sheet for an RPG. The character has a strength score and an agility score. You need to implement the following logic:

  • Create a Character class with stored properties for strength and agility.
  • Create a computed property called combatPower that returns the sum of strength and agility multiplied by 1.5.
  • Add a property observer to strength so that whenever it is updated, it prints: "Strength changed from [oldValue] to [newValue]!"
  • Add a property observer to agility that prints a warning "Agility is too low!" only if the new value is below 5.

Test your implementation by creating a character, changing their strength, and dropping their agility below 5 to ensure the observers trigger correctly.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.