Design Patterns in Swift — Observer

Ruslan Dzhafarov
5 min readJan 23, 2023

--

The Observer design pattern is a software design pattern that allows an object, called the subject, to notify other objects, called observers, of any changes to its state. This pattern is useful in situations where one object needs to be aware of changes in another object, without having a tightly coupled relationship between the two. In this article, we will discuss the Observer design pattern and provide examples of its use in Swift.

The basic components of the Observer pattern are the subject, which is the object that is being observed, and the observer, which is the object that is interested in being notified of changes in the subject. The subject maintains a list of its observers and provides methods for adding and removing observers from the list. The observer typically implements an update method that is called by the subject when a change occurs.

One real-world example of the Observer pattern is a weather station. The weather station is the subject and it maintains a list of observers such as weather apps and websites. When the weather station receives new data, it updates its internal state and then notifies all the observers on the list, so they can update their information accordingly.

In Swift, we can implement the Observer pattern by creating a protocol for the observer and a class for the subject. The observer protocol defines the update method that will be called by the subject when a change occurs. The subject class maintains a list of observers and provides methods for adding and removing observers from the list.

protocol WeatherObserver: AnyObject {
func update(temp: Double, humidity: Double, pressure: Double)
}

class WeatherStation {
var temperature: Double = 0.0
var humidity: Double = 0.0
var pressure: Double = 0.0
var observers: [WeatherObserver] = []

func registerObserver(_ observer: WeatherObserver) {
observers.append(observer)
}

func removeObserver(_ observer: WeatherObserver) {
if let index = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: index)
}
}

func notifyObservers() {
for observer in observers {
observer.update(temp: temperature, humidity: humidity, pressure: pressure)
}
}

func setMeasurements(temp: Double, humidity: Double, pressure: Double) {
self.temperature = temp
self.humidity = humidity
self.pressure = pressure
notifyObservers()
}
}

In this example, the WeatherObserver protocol defines the update method that takes three parameters: temperature, humidity, and pressure. The WeatherStation class maintains a list of WeatherObserver objects and provides methods for adding and removing observers from the list. When the WeatherStation receives new data, it updates its internal state and then calls the notifyObservers method, which in turn calls the update method on each observer in the list, passing the new data as parameters.

Now, let’s say we have a weather app that wants to display the current weather conditions. The weather app can create an instance of the WeatherStation class and register itself as an observer, so that it can be notified of any changes in the weather station's state.

class WeatherApp: WeatherObserver {
func update(temp: Double, humidity: Double, pressure: Double) {
print("WeatherApp: New weather data received: temp \(temp) humidity \(humidity) pressure (pressure)")
}
}

let weatherStation = WeatherStation()
let weatherApp = WeatherApp()

//register the observer
weatherStation.registerObserver(weatherApp)

//set the measurements and notify the observer
weatherStation.setMeasurements(temp: 72.0, humidity: 65.0, pressure: 1013.25)
// Output: "WeatherApp: New weather data received: temp 72.0 humidity 65.0 pressure 1013.25"

//remove the observer
weatherStation.removeObserver(weatherApp)

//set the measurements again
weatherStation.setMeasurements(temp: 75.0, humidity: 70.0, pressure: 1015.0)
// Output: nothing

In this example, the `WeatherApp` class conforms to the `WeatherObserver` protocol and implements the update method. The `WeatherApp` creates an instance of the `WeatherStation` and register itself as an observer. When the `WeatherStation` receives new data, it notifies all the observers and call the update method of the observer and pass the new data as parameters.

Another example of the Observer pattern is in a chat application, where a chat room is the subject and the chat clients are the observers. The chat room maintains a list of chat clients and when a new message is received, it notifies all the clients on the list, so they can display the new message. In this example, the chat client can be the observer, and the chat room can be the subject. The chat room maintains a list of chat clients and provides methods for adding and removing clients from the list. The chat client typically implements an update method that is called by the chat room when a new message is received.

In Swift, we can implement the Observer pattern for this example in a similar way as the weather station example, by creating a protocol for the observer and a class for the subject. The observer protocol defines the update method that will be called by the subject when a new message is received. The subject class maintains a list of observers and provides methods for adding and removing observers from the list.

protocol ChatClientObserver {
func update(message: String)
}

class ChatRoom {
var message: String = ""
var observers: [ChatClientObserver] = []

func registerObserver(_ observer: ChatClientObserver) {
observers.append(observer)
}

func removeObserver(_ observer: ChatClientObserver) {
if let index = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: index)
}
}

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

func receiveMessage(message: String) {
self.message = message
notifyObservers()
}
}

In this example, the ChatClientObserver protocol defines the update method that takes a parameter: message. The ChatRoom class maintains a list of ChatClientObserver objects and provides methods for adding and removing observers from the list. When the ChatRoom receives a new message, it updates its internal state and then calls the notifyObservers method, which in turn calls the update method on each observer in the list, passing the new message as a parameter.

Thank you for reading until the end. If you have any questions or feedback, feel free to leave a comment below.

If you enjoyed reading this article, please press the clap button 👏 . Your support encourages me to share more of my experiences and write more articles like this. Follow me here on medium for more updates!

Happy coding! 😊👨‍💻👩‍💻

More from Ruslan Dzhafarov

Advanced UIKit Techniques

9 stories

Design Patterns in Swift

9 stories

Advanced Swift Programming

3 stories

--

--

Ruslan Dzhafarov

Senior iOS Developer since 2013. Sharing expert insights, best practices, and practical solutions for common development challenges