Observer in Swift


Introduction to Observers in Swift

An observer in Swift is a mechanism that allows objects to monitor changes in data and respond accordingly. Observers are used in various scenarios, such as:

  • Tracking property changes
  • Reacting to notifications
  • Building dynamic UI updates

This guide explores Swift Observers with separate examples and outputs.

1. Property Observers in Swift

Swift provides two built-in property observers:

  • willSet – Executes before a property’s value changes.
  • didSet – Executes after a property’s value changes.

Example 1: Tracking Property Changes with didSet

class User {
     var name: String {
         didSet {
             print("User name changed from \(oldValue) to \(name)")
         }
     }

     init(name: String) {
         self.name = name
     }
}

let user = User(name: "John")
user.name = "David" // Changing value

Output

User name changed from John to David

oldValue stores the previous value.
didSet triggers after the value changes.

Example 2: Using willSet to Observe Changes Before They Happen

class Account {
     var balance: Double = 0.0 {
         willSet(newAmount) {
             print("Balance will change from \(balance) to \(newAmount)")
         }
     }
}

let myAccount = Account()
myAccount.balance = 1000.0

Output

Balance will change from 0.0 to 1000.0

willSet(newAmount) runs before the value changes.
Useful for validations and logging changes.

2. Using NotificationCenter for Observing Events

NotificationCenter is used for broadcasting messages across different parts of an app.

Example 3: Sending and Receiving Notifications

import Foundation

// Define Notification Name
extension Notification.Name {
     static let dataUpdated = Notification.Name("dataUpdated")
}

// Observer Class
class Observer {
     init() {
         NotificationCenter.default.addObserver(self, selector: #selector(handleUpdate), name: .dataUpdated, object: nil)
     }

     @objc func handleUpdate(notification: Notification) {
         print("Data Updated Notification Received!")
     }
}

// Sender Class
class DataManager {
     func updateData() {
         print("Data is being updated...")
         NotificationCenter.default.post(name: .dataUpdated, object: nil)
     }
}

// Usage
let observer = Observer()
let dataManager = DataManager()

dataManager.updateData()

Output

Data is being updated...
Data Updated Notification Received!

NotificationCenter.default.post(...) sends notifications.
NotificationCenter.default.addObserver(...) listens for notifications.

3. Observing Changes with KVO (Key-Value Observing)

KVO (Key-Value Observing) allows you to monitor property changes dynamically.

Example 4: Using KVO to Observe a Property

import Foundation

class Car: NSObject {
     @objc dynamic var speed: Int = 0
}

class SpeedObserver: NSObject {
     var car: Car

     init(car: Car) {
         self.car = car
         super.init()
         car.addObserver(self, forKeyPath: "speed", options: [.old, .new], context: nil)
     }

     override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
         if keyPath == "speed" {
             print("Speed changed from \(change?[.oldKey] ?? 0) to \(change?[.newKey] ?? 0)")
         }
     }
}

// Usage
let car = Car()
let observer = SpeedObserver(car: car)

car.speed = 60
car.speed = 80

Output

Speed changed from 0 to 60
Speed changed from 60 to 80

@objc dynamic var enables KVO compatibility.
addObserver(...) registers for property change notifications.

4. Observing Combine Publishers (SwiftUI & Functional Programming)

With Combine framework, observers handle real-time data streams.

Example 5: Using Combine to Observe Data Changes

import Combine

class User {
     @Published var age: Int = 0
}

let user = User()
let subscription = user.$age.sink { newAge in
     print("User age changed to \(newAge)")
}

user.age = 25
user.age = 30

Output

User age changed to 25
User age changed to 30

@Published automatically publishes changes.
.sink {} reacts to new values dynamically.

5. Observer Design Pattern (Custom Implementation)

Sometimes, you may need a manual observer pattern for decoupled communication.

Example 6: Creating a Custom Observer Pattern

protocol Observer {
     func update(message: String)
}

class Subject {
     private var observers: [Observer] = []

     func addObserver(_ observer: Observer) {
         observers.append(observer)
     }

     func notifyObservers(message: String) {
         for observer in observers {
             observer.update(message: message)
         }
     }
}

// Observer Implementation
class ConcreteObserver: Observer {
     func update(message: String) {
         print("Observer received message: \(message)")
     }
}

// Usage
let subject = Subject()
let observer1 = ConcreteObserver()
let observer2 = ConcreteObserver()

subject.addObserver(observer1)
subject.addObserver(observer2)

subject.notifyObservers(message: "New event happened!")

Output

Observer received message: New event happened!
Observer received message: New event happened!

notifyObservers() sends updates to all registered observers.
Useful for event-driven programming.

6. When to Use Observers in Swift?

Use Case Best Observer Type
Property Change Tracking willSet / didSet
Broadcasting Events NotificationCenter
Monitoring Dynamic Properties KVO (Key-Value Observing)
SwiftUI / Reactive Programming Combine Framework
Custom Decoupled Communication Custom Observer Pattern

Choose the right observer based on your Swift application needs.

Conclusion

Observers in Swift enhance data flow and event-driven programming. Whether using property observers, NotificationCenter, KVO, or Combine, each method provides efficient event tracking.