Observer Design Pattern in iOS

Jaime Escobar
3 min readJul 21, 2024

--

Sponge Bob Square Pants’ Patrick Glasses meme

The Observer (a.k.a. Listener) is a behavioral design pattern that allows to notify multiple objects subscribed to a publisher which is going to advise all subscribers with the new state.

How to implement

This is an easy to implement pattern, but first it is needed to understand some key components and their relationship.

  • Publishers. These are the objects that will update the new values and notify any subscribers they have.
  • Subscribers. These are the listeners that will receive the information notified by the publisher they are subscribed to.

Note: This relationship is depicted as one-to-many from Publisher’s perspective since that’s how the data flows in the pattern.

In a first instance, the publisher and subscriber interfaces are define as following:

public protocol Publisher<T> {
associatedtype T
associatedtype Subscriptor
func add(subscriptor: Subscriptor)
func remove(subscriptor: Subscriptor)
func publish(new: T)
}

public protocol Subscriber<T>: Identifiable, Hashable {
associatedtype T
var id: UUID { get }
func update(with new: T)
}

This will allow to have a publisher that could add multiple subscribers that expect the type of data the publisher is design to notify.

Now, the concrete implementations will conform to these protocols to define a publisher and its subscribers.

struct News: Hashable {
let title: String
let content: String
}

class NewsPublisher: Publisher {
typealias T = News
typealias Subscriptor = NewsSubscriber

private var subscribers: Set<Subscriptor> = .init()
private var new: T?

func add(subscriptor: Subscriptor) {
self.subscribers.insert(subscriptor)
}

func remove(subscriptor: Subscriptor) {
self.subscribers.remove(subscriptor)
}

func publish(new: T) {
self.new = new
subscribers.forEach { subscriber in
subscriber.update(with: new)
}
}
}

class NewsSubscriber: Identifiable, Subscriber {
typealias T = News

let id = UUID()
private var news: T?

func update(with new: T) {
debugPrint("New news recieved: \(new.title); \(new.content)")
}
}

// In order to conform to hashable and be able to compare them.
extension NewsSubscriber: Equatable, Hashable {
static func == (lhs: NewsSubscriber, rhs: NewsSubscriber) -> Bool {
lhs.id == rhs.id
}

func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}

In this example, the publisher will notify all subscribers that will expect a News object.

let news = News(
title: "2024 CrowdStrike incident",
content: "On 19 July 2024, a faulty update to security software produced by CrowdStrike, an American cybersecurity company, caused widespread problems as computers and virtual machines running Microsoft Windows crashed and were unable to properly restart."
)

let cnn = NewsPublisher()
let customer = NewsSubscriber()

cnn.add(subscriptor: customer)
cnn.publish(new: news)

Then the subscribers will receive this update and perform an action with it. In this case, the News are printed.

When a subscriber is not longer requiring updates, it can be removed from the publisher and stop receiving News.

cnn.remove(subscriptor: customer)

let anotherNew = News(
title: "DeepSeek shakes up AI sector",
content: "A new open-source artificial intelligence (AI) model developed by Chinese start-up DeepSeek sent waves through the global tech community last month, offering similar performance to other leading models at a fraction of the cost."
)

cnn.publish(new: anotherNew)

// No subscribers, so the news got broadcasted to anyone

Advantages and considerations

These are some of the advantages the implementation of this design pattern will provide when used.

  • Follows the Open/Closed principle. Since it lets to add more subscribers as needed without changing the publisher’s current implementation.
  • Adds flexibility. Because the new subscribers can be added at runtime an removed with an unsubscribe method.

There are also some considerations to take in mind.

  • Adds complexity. Since multiple subscribers could be added, it is imperative to control when and how they subscribe to the publishers. Also some subscribers could become publishers when they digest and notify the updates to other subscribers.
  • Retain cycles. As the reference to the subscribers are hold within the publisher in order to get notified, it should be considered mechanisms to avoid this issues such as weak references or having un-subscription methods.

Conclusion

As shown above, this design pattern is commonly used in reactive frameworks such as RxSwift, and Combine (from where SwiftUI gets its magic), in which publishers and subscribers are implemented to define a behavior of waiting for updates to perform changes like updating the UI or saving to a database.

--

--

Jaime Escobar
Jaime Escobar

Written by Jaime Escobar

Software Engineer 👨🏽‍💻 | iOS Developer 📱 | SwiftUI Lover 💙

Responses (1)