Event Bus and Observer Pattern in Swift (iOS/macOS)
In iOS and macOS development, there is often a need to organize loosely coupled interaction between application components. The Observer pattern and its implementation as an Event Bus is a powerful tool for solving this problem. In this article, we will thoroughly examine how to implement an Event Bus in Swift using modern language features (Generics, closures, weak references).
What is the Observer Pattern?
The Observer pattern defines a "one-to-many" relationship between objects. When one object (the publisher) changes its state, all dependent objects (subscribers) are automatically notified and updated. This avoids tight coupling and simplifies code maintenance.
In Swift, native implementations of Observer are:
NotificationCenter— a global notification bus built into Foundation.- Key-Value Observing (KVO) — a mechanism for observing object properties.
- Combine framework (iOS 13+) — a reactive framework with support for publishers and subscribers.
However, a lighter and type-safe implementation is often required, especially when working with legacy code or in projects where Combine is not available.
Event Bus: A Simple and Flexible Implementation
An Event Bus is a central event dispatcher that allows subscribing to events and sending them without direct knowledge of the subscribers. We will implement it using generics and weak references to avoid memory leaks.
Basic Event Bus Implementation
import Foundation
// Protocol for all eventsprotocol Event { }
// Specific event typesstruct UserLoggedInEvent: Event { let userId: String let userName: String}
struct DataLoadedEvent: Event { let data: [String]}
// Event Bus classfinal class EventBus { static let shared = EventBus() private init() {} // Dictionary to store subscribers private var subscribers = [ObjectIdentifier: [Any]]() // Subscribe to an event func subscribe<T: Event>(observer: AnyObject, handler: @escaping (T) -> Void) { let key = ObjectIdentifier(T.self) let wrapped = Subscription(observer: observer, handler: handler as Any) if subscribers[key] != nil { subscribers[key]?.append(wrapped) } else { subscribers[key] = [wrapped] } } // Unsubscribe from all events for a specific observer func unsubscribe(observer: AnyObject) { for key in subscribers.keys { subscribers[key]?.removeAll(where: { subscription in if let sub = subscription as? Subscription { return sub.observer === observer } return false }) } } // Publish an event func publish<T: Event>(event: T) { let key = ObjectIdentifier(T.self) guard let subs = subscribers[key] else { return } for subscription in subs { if let sub = subscription as? Subscription { if let handler = sub.handler as? (T) -> Void { handler(event) } } } }}
// Helper structure for storing subscriptionsprivate struct Subscription { weak var observer: AnyObject? let handler: Any init(observer: AnyObject, handler: Any) { self.observer = observer self.handler = handler }}Using the Event Bus
// ViewController that subscribes to eventsclass LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Subscribe to the event