Event Bus и паттерн Observer в Swift (iOS/macOS)
В разработке под iOS и macOS часто возникает необходимость организовать слабосвязанное взаимодействие между компонентами приложения. Паттерн Observer и его реализация через шину событий (Event Bus) — это мощный инструмент для создания гибкой и масштабируемой архитектуры. В этой статье мы разберем, как реализовать Event Bus на Swift, сравним его с встроенными механизмами Apple (NotificationCenter, Combine) и покажем практические примеры для iOS/macOS.
Что такое Event Bus и паттерн Observer?
Паттерн Observer определяет отношение "один ко многим" между объектами: когда один объект (издатель) изменяет свое состояние, все зависимые объекты (подписчики) автоматически уведомляются об этом.
Event Bus (шина событий) — это централизованный механизм, который позволяет любому компоненту отправлять события (публиковать) и получать их (подписываться), не зная друг о друге напрямую. Это реализует принцип слабой связанности (loose coupling).
В экосистеме Apple уже есть встроенные решения:
- NotificationCenter — классическая реализация Observer, но с ограничениями по типизации и безопасности.
- Combine — современный фреймворк для реактивного программирования, предоставляющий мощные Publisher/Subscriber.
Однако самописный Event Bus дает полный контроль и может быть легче встроен в legacy-проекты.
Реализация простого Event Bus на Swift
Создадим универсальную шину событий с использованием дженериков и слабых ссылок для предотвращения утечек памяти.
import Foundation
// Протокол для событий
protocol Event { }
// Реализация Event Bus
final class EventBus {
static let shared = EventBus()
private var observers = [ObjectIdentifier: [WeakObserver]]()
private init() { }
// Подписка на событие
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]
}
// Очистка мертвых ссылок
cleanDeadObservers(for: id)
}
// Публикация события
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)
}
// Удаляем освобожденные объекты
cleanDeadObservers(for: id)
}
// Отписка
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 }
}
}
// Вспомогательный класс для слабой ссылки
private class WeakObserver {
weak var observer: AnyObject?
var handler: ((Event) -> Void)?
init(observer: AnyObject, handler: @escaping (Event) -> Void) {
self.observer = observer
self.handler = handler
}
}
Пример использования
Определим пользовательские события:
struct UserLoggedInEvent: Event {
let userId: String
let userName: String
}
struct DataLoadedEvent: Event {
let data: [String]
}
Использование в ViewController:
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Подписка на событие входа
EventBus.shared.subscribe(observer: self) { [weak self] (event: UserLoggedInEvent) in
print("Пользователь \(event.userName) вошел в систему")
self?.updateUI()
}
// Подписка на загрузку данных
EventBus.shared.subscribe(observer: self) { (event: DataLoadedEvent) in
print("Загружено \(event.data.count) элементов")
}
}
deinit {
// Автоматическая отписка при уничтожении объекта
// (благодаря слабым ссылкам в EventBus)
}
private func updateUI() {
// Обновление интерфейса
}
}
// Где-то в другом модуле
class AuthService {
func login(userId: String, name: String) {
// Логика авторизации...
EventBus.shared.publish(UserLoggedInEvent(userId: userId, userName: name))
}
}
Сравнение с NotificationCenter и Combine
| Характеристика | Event Bus (наш) | NotificationCenter | Combine |
|---|---|---|---|
| Типобезопасность | Да (дженерики) | <