How to Use Property Observers in Swift

What you’ll build or solve

You’ll add didSet and willSet to stored properties so your code can react to assignments automatically.

When this approach works best

Property observers work best when:

  • You want to react to changes right where the property is defined, like recording changes for debugging.
  • You need a quick hook to trigger a small side effect when a value updates.
  • You want to inspect an incoming value before saving it, or compare old and new values.

This is a bad idea when the work is expensive, async, or full of business rules. In that case, a method call or a separate type usually keeps the code easier to follow.

Prerequisites

  • Xcode or a Swift Playground
  • Basic knowledge of Swift stored properties (var) in structs or classes

Step-by-step instructions

Step 1: Use didSet to react after a value changes

didSet runs after Swift stores the new value. You can read the previous value using oldValue.

structScoreBoard {
varscore:Int=0 {
didSet {
print("Score changed from \(oldValue) to \(score)")
        }
    }
}

What to look for

  • oldValue is available automatically inside didSet.
  • score already contains the new value inside didSet.

Step 2: Use willSet to react before a value changes

willSet runs before Swift stores the new value. You can read the incoming value using newValue.

structTemperature {
varcelsius:Double=0 {
willSet {
print("About to change to \(newValue)°C")
        }
    }
}

What to look for

  • newValue is available automatically inside willSet.
  • The property still contains the old value inside willSet.

Step 3: Know what to look for with observers

Observers stay small and predictable when you remember a few rules and patterns.

What to look for

  • You can combine both on one property when you want a “before and after” hook.
structPlayer {
varhealth:Int=100 {
willSet {
print("Health will change to \(newValue)")
        }
didSet {
ifhealth<=0 {
print("Player defeated")
            }
        }
    }
}
  • Common uses: lightweight logging, simple UI refresh triggers, basic validation or clamping, and analytics counters.
  • Reassigning inside didSet can be used for clamping in Swift. Swift does not recursively trigger the observer from that reassignment.
  • Observers work only with stored properties, not computed properties.
  • Observers do not run during initialization when setting initial values.
  • Observers work in both structs and classes, the syntax stays the same.

Examples you can copy

Example 1: Simple change log

structSettings {
varisDarkMode:Bool=false {
didSet {
print("Dark mode changed from \(oldValue) to \(isDarkMode)")
        }
    }
}

varsettings=Settings()
settings.isDarkMode=true

Example 2: Inspect incoming values

structBankAccount {
varbalance:Double=0 {
willSet {
print("Balance will become \(newValue)")
        }
    }
}

varaccount=BankAccount()
account.balance=50
account.balance=120

Example 3: Clamping with didSet

structVolume {
varlevel:Int=5 {
didSet {
iflevel<0 {level=0 }
iflevel>10 {level=10 }
        }
    }
}

varvolume=Volume()
volume.level=20
print(volume.level)

Common mistakes and how to fix them

Mistake 1: Using observers on a computed property

What you might do:

structExample {
vartotal:Int {
didSet {
print("Changed")
        }
get {0 }
set { }
    }
}

Why it breaks: Swift only allows observers on stored properties, and this is a computed property.

Correct approach:

structExample {
vartotal:Int=0 {
didSet {
print("Changed")
        }
    }
}

Mistake 2: Expecting observers to run during initialization

What you might do:

structUser {
varage:Int=0 {
didSet {
print("Age updated")
        }
    }

init(age:Int) {
self.age=age
    }
}

Why it breaks: Swift does not run willSet or didSet while initializing stored properties.

Correct approach:

structUser {
varage:Int=0 {
didSet {
print("Age updated")
        }
    }

init(age:Int) {
self.age=age
print("Age set during init")
    }
}

Troubleshooting

  • If you see Cannot use property observers with a computed property, switch to a stored property or move logic into the setter.
  • If your observer never runs, confirm you are assigning a new value after initialization, not only setting the initial value.
  • If your observer prints unexpected values, check which observer you used. willSet reads newValue, didSet reads oldValue.
  • If code feels hard to follow, keep observer bodies short and move complex work into methods.

Quick recap

  • Use didSet to run code after a stored property changes.
  • Use willSet to run code before a stored property changes.
  • Read oldValue in didSet and newValue in willSet.
  • Observers apply to stored properties, not computed properties.
  • Observers do not run during initialization.
  • Keep observer logic small, move complex rules into methods.