- API fetch
- Array
- Async await
- Class
- Closures
- Computed property
- Concurrency
- Constants
- Data types
- Defer statement
- Dictionary
- Enum
- Escaping closure
- Extension
- For loop
- forEach
- Function
- Generics
- Guard statement
- if let statement
- Inheritance
- inout
- Lazy var
- Operator
- Optionals
- Property observers
- Property wrapper
- Protocol
- String formatting
- String interpolation
- Struct
- Switch statement
- Try catch
- Tuple
- Variables
- While loop
SWIFT
Swift Property Observers: Syntax, Use Cases, and Best Practices
Property observers in Swift let you monitor and respond to changes in a property’s value. They allow you to run custom logic either before or after a value is modified, making them ideal for tasks like updating the UI, logging changes, validating input, or syncing state between components.
Unlike computed properties, observers apply to stored properties—those that hold actual values—and are triggered every time a value is assigned, even if it hasn’t changed.
Syntax Overview
Swift supports two types of observers:
willSet
: Called just before a new value is assigned.didSet
: Called immediately after the assignment.
Here’s an example using both:
var score: Int = 0 {
willSet {
print("Score will change from \(score) to \(newValue)")
}
didSet {
print("Score changed from \(oldValue) to \(score)")
}
}
Assigning a value triggers both blocks:
score = 10
// Output:
// Score will change from 0 to 10
// Score changed from 0 to 10
You can use just one observer if the other isn’t needed.
Using newValue
and oldValue
Inside the willSet
block, the incoming value is available through the newValue
keyword, which you can rename for clarity:
willSet(newScore) {
print("Preparing to set new score: \(newScore)")
}
In the didSet
block, oldValue
gives you access to the previous value. These keywords let you compare old and new values and respond accordingly.
Applying Observers to Stored Properties
You can attach observers to stored properties in both structs and classes:
struct Temperature {
var celsius: Double {
didSet {
print("Celsius updated to \(celsius)")
}
}
}
Every time celsius
is updated, the didSet
block runs. This is useful when state changes must propagate through different parts of your app.
Practical Use Cases
Property observers are commonly used for:
UI Updates
var username: String = "" {
didSet {
nameLabel.text = username
}
}
Logging Changes
var loggedIn: Bool = false {
didSet {
print("User login state changed: \(loggedIn)")
}
}
Input Validation
var quantity: Int = 1 {
didSet {
if quantity < 1 {
quantity = 1
}
}
}
This example ensures that quantity
can’t be less than 1.
State Synchronization
var userID: Int = 0 {
didSet {
fetchUserDetails(for: userID)
}
}
You can trigger side effects like API calls when a property changes.
Observing Optional Properties
Observers also work with optionals. Even assigning nil
or reassigning the same value will trigger them:
var token: String? {
didSet {
print("Auth token updated.")
}
}
Inheritance Considerations
Observers aren’t inherited by subclasses automatically. If a subclass wants to observe a property, it must override it explicitly:
class Base {
var name: String = "" {
didSet {
print("Base updated name")
}
}
}
class Child: Base {
override var name: String {
didSet {
print("Child updated name")
}
}
}
Without the override, the subclass won’t execute the base class’s observer logic.
Behavior During Initialization
Observers do not get called when a property is initialized within the same scope:
var message: String = "Hello" {
didSet {
print("Message changed")
}
}
init() {
message = "Welcome" // didSet won’t run here
}
However, if a superclass assigns a value to a subclass property that has observers, those observers will be triggered.
Static Properties and Observers
Swift doesn’t support observers on static (type-level) stored properties:
static var count: Int = 0 // ❌ Not allowed
As a workaround, you can use a computed property with custom getter/setter logic to simulate observation.
Observers vs. Computed Properties
Understanding the distinction is important:
- Computed properties don’t store a value; they compute it on access.
- Stored properties with observers hold actual values and execute logic on assignment.
Example:
var age: Int = 0 {
didSet {
print("Age updated")
}
}
var isAdult: Bool {
return age >= 18
}
Use observers when you need to monitor and react to changes. Use computed properties when the value is derived from other properties.
Property Wrappers and Observers
When using wrappers like @Published
or @State
in SwiftUI, you can’t attach willSet
or didSet
directly to the wrapped property. Instead, rely on Combine publishers or .onChange(of:)
to react to changes:
.onChange(of: token) { newToken in
print("Token changed to \(newToken)")
}
Best Practices
Keep these tips in mind:
- Avoid infinite loops — Don’t assign to the same property inside
didSet
. - Keep logic lightweight — Use observers for side effects, not heavy processing.
- Don’t expect them to run during init — They only trigger on post-initialization changes.
- Use for side effects only — Don’t compute return values or mutate unrelated state arbitrarily.
Summary
Property observers in Swift give you fine-grained control over how your app responds to data changes. Using willSet
and didSet
, you can update UI, enforce constraints, trigger sync logic, or log events—all with minimal boilerplate.
While they aren’t a one-size-fits-all tool, knowing when and how to use observers can help you write more responsive and maintainable code. In SwiftUI and beyond, they’re a foundational tool in the reactive programming toolbox.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.