Event Bus and Observer Pattern in Swift (iOS/macOS)

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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 via an event bus (Event Bus) is a powerful tool for creating flexible and scalable architecture. In this article, we will analyze how to implement an Event Bus in Swift, compare it with Apple's built-in mechanisms (NotificationCenter, Combine), and show practical examples for iOS/macOS.



What is Event Bus and 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.



Event Bus is a centralized mechanism that allows any component to send events (publish) and receive them (subscribe) without knowing about each other directly. This implements the principle of loose coupling.



In the Apple ecosystem, there are already built-in solutions:

  • NotificationCenter — a classic implementation of Observer, but with limitations in typing and safety.
  • Combine — a modern framework for reactive programming, providing powerful Publisher/Subscriber capabilities.

However, a custom Event Bus provides full control and can be more easily integrated into legacy projects.



Implementing a Simple Event Bus in Swift



Let's create a universal event bus using generics and weak references to prevent memory leaks.



import Foundation

// Protocol for eventsprotocol Event { }

// Event Bus implementationfinal class EventBus { static let shared = EventBus() private var observers = [ObjectIdentifier: [WeakObserver]]() private init() { } // Subscribe to an event func subscribe<T: Event>(observer: AnyObject, handler: @escaping (T) -> Void) { let id = ObjectIdentifier(T.self) let weakRef = WeakObserver(observer: observer, handler: { event in guard let castedEvent = event as? T else { return } handler(castedEvent) }) if observers[id] != nil { observers[id]?.append(weakRef) } else { observers[id] = [weakRef] } // Clean up dead references cleanDeadObservers(for: id) } // Publish an event func publish<T: Event>(_ event: T) { let id = ObjectIdentifier(T.self) guard let observerList = observers[id] else { return } for weakObserver in observerList { weakObserver.handler?(event) } // Remove deallocated objects cleanDeadObservers(for: id) } // Unsubscribe func unsubscribe(observer: AnyObject, eventType: Event.Type) { let id = ObjectIdentifier(eventType) observers[id]?.removeAll { $0.observer === observer } } private func cleanDeadObservers(for id: ObjectIdentifier) { observers[id]?.removeAll { $0.observer == nil } }}

// Helper class for weak referenceprivate class WeakObserver { weak var observer: AnyObject? var handler: ((Event) -> Void)? init(observer: AnyObject, handler: @escaping (Event) -> Void) { self.observer = observer self.handler = handler }}


Usage Example



Define custom events:



struct UserLoggedInEvent: Event {    let userId: String    let userName: String}

struct DataLoadedEvent: Event { let data: [String]}


Usage in a ViewController:



class ProfileViewController: UIViewController {    override func                    

Recommendations