Event Bus и паттерн Observer в Swift (iOS/macOS)

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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
Типобезопасность Да (дженерики)