So far we've seen how to store values in properties and calculate them dynamically with computed properties. But what if you want to react every time a property changes โ automatically, without having to remember to call a function yourself? That's exactly what property observers are for. ๐ฅ
Swift gives us two flavors:
-
didSetโ runs after the property changes -
willSetโ runs before the property changes
The Problem Without Property Observers
Imagine we're tracking a ninja's battle score:
struct BattleTracker {
var score = 0
}
var naruto = BattleTracker()
naruto.score += 10
print("Score is now \(naruto.score)")
naruto.score -= 3
print("Score is now \(naruto.score)")
naruto.score += 1
// Oops โ forgot to print this one! ๐
We have to remember to print the score every time it changes. Forget once โ like that last line โ and you've got a bug. In a small example that's easy to spot, but in a real app with dozens of places updating a property? That's a disaster waiting to happen.
didSet โ React After the Change
With didSet, we attach the print directly to the property. Now it runs every time the score changes, no matter where the change happens:
struct BattleTracker {
var score = 0 {
didSet {
print("Score updated to \(score)!")
}
}
}
var naruto = BattleTracker()
naruto.score += 10 // "Score updated to 10!"
naruto.score -= 3 // "Score updated to 7!"
naruto.score += 1 // "Score updated to 8!"
No more forgotten print statements. Swift handles it automatically. ๐
Accessing the Old Value
Inside didSet, Swift automatically gives you oldValue โ the value the property had before the change. This is useful when you want to show what changed:
struct Ninja {
var chakraLevel = 1000 {
didSet {
print("Chakra changed from \(oldValue) to \(chakraLevel)")
}
}
}
var sasuke = Ninja()
sasuke.chakraLevel -= 300 // "Chakra changed from 1000 to 700"
sasuke.chakraLevel -= 150 // "Chakra changed from 700 to 550"
oldValue is automatically provided โ you don't need to declare it yourself.
willSet โ React Before the Change
willSet works the same way but fires before the property changes. Inside it, Swift automatically provides newValue โ the value that's about to be assigned:
struct Guild {
var members = [String]() {
willSet {
print("Current members: \(members)")
print("About to change to: \(newValue)")
}
didSet {
print("Guild now has \(members.count) members.")
print("Previous list was: \(oldValue)")
}
}
}
var akatsuki = Guild()
akatsuki.members.append("Pain")
akatsuki.members.append("Konan")
Both willSet and didSet fire when you append to an array โ because appending is a change to the property.
willSet vs didSet โ Which Should You Use?
In practice, didSet is what you'll use most of the time. The reason is simple: you usually want to react after something has changed โ update the UI, save data, log something.
willSet is less common, but it shines when you need to capture the state before the change happens. SwiftUI itself uses willSet in certain places for animations โ it takes a snapshot of the UI before the change, then compares it with the "after" snapshot to figure out what needs to animate.
A quick summary:
| Observer | Fires | Automatic value available |
|---|---|---|
willSet |
Before the change |
newValue (the value coming in) |
didSet |
After the change |
oldValue (the value that just left) |
Why Use Observers Instead of Just Calling a Function?
You might be thinking: "couldn't I just call a function manually every time I update a property?" Yes โ but then you have to remember to do it. Every. Single. Time.
// Without observers โ easy to forget:
naruto.chakraLevel -= 300
logChakraChange() // what if you forget this line?
naruto.chakraLevel -= 150
// Forgot to call logChakraChange() here โ silent bug ๐
With a didSet observer, you only write the logic once, attached to the property itself. Swift guarantees it runs every time the property changes, no matter where in your code that happens. You're transferring the responsibility to Swift so your brain can focus on more interesting things. ๐ธ
One Important Warning
Keep property observers lightweight. If score += 1 looks like a simple operation, the reader of your code (including future you) will expect it to be fast. If your didSet runs an expensive network call or sorts a massive array every time a number increments, you'll get mysterious slowdowns that are hard to trace.
A good rule of thumb: property observers should do quick reactive work โ logging, updating a UI label, playing a sound effect. Heavy work belongs in a function you call explicitly, where the cost is obvious. ๐ธ
Wrap Up
| Concept | What It Means |
|---|---|
didSet |
Runs after a property changes; oldValue is available |
willSet |
Runs before a property changes; newValue is available |
oldValue |
The value the property had before the change (inside didSet) |
newValue |
The value about to be assigned (inside willSet) |
| Why observers? | Swift guarantees they run โ no forgetting to call a function |
This article was written by me; AI was used to improve grammar and readability.
Top comments (0)