Event Bus et le pattern Observer en Swift (iOS/macOS)
\n\nDans le développement iOS et macOS, il est souvent nécessaire d'organiser une interaction faiblement couplée entre les composants de l'application. Le pattern Observer et son implémentation sous forme d'Event Bus (bus d'événements) sont un outil puissant pour résoudre ce problème. Dans cet article, nous allons détailler comment implémenter un Event Bus en Swift, en utilisant les fonctionnalités modernes du langage (Generics, closures, weak references).
\n\nQu'est-ce que le pattern Observer ?
\n\nLe pattern Observer (Observateur) définit une relation "un à plusieurs" entre les objets. Lorsqu'un objet (l'éditeur) modifie son état, tous les objets qui en dépendent (les abonnés) sont automatiquement notifiés et mis à jour. Cela permet d'éviter un couplage fort et de simplifier la maintenance du code.
\n\nEn Swift, les implémentations natives de l'Observer sont :
\n- \n
NotificationCenter— un bus de notifications global, intégré à Foundation. \n - Key-Value Observing (KVO) — un mécanisme d'observation des propriétés des objets. \n
- Combine framework (iOS 13+) — un framework réactif avec prise en charge des éditeurs et des abonnés. \n
Cependant, une implémentation plus légère et type-safe est souvent nécessaire, en particulier lorsqu'on travaille avec du code legacy ou dans des projets où Combine n'est pas disponible.
\n\nEvent Bus : une implémentation simple et flexible
\n\nL'Event Bus est un répartiteur central d'événements qui permet de s'abonner à des événements et de les envoyer sans connaissance directe des abonnés. Nous allons l'implémenter en utilisant des génériques et des références faibles pour éviter les fuites mémoire.
\n\nImplémentation de base de l'Event Bus
\n\nimport Foundation\n\n// Protocole pour tous les événements\nprotocol Event { }\n\n// Types d'événements concrets\nstruct UserLoggedInEvent: Event {\n let userId: String\n let userName: String\n}\n\nstruct DataLoadedEvent: Event {\n let data: [String]\n}\n\n// Classe Event Bus\nfinal class EventBus {\n static let shared = EventBus()\n private init() {}\n \n // Dictionnaire pour stocker les abonnés\n private var subscribers = [ObjectIdentifier: [Any]]()\n \n // Abonnement à un événement\n func subscribe<T: Event>(observer: AnyObject, handler: @escaping (T) -> Void) {\n let key = ObjectIdentifier(T.self)\n let wrapped = Subscription(observer: observer, handler: handler as Any)\n \n if subscribers[key] != nil {\n subscribers[key]?.append(wrapped)\n } else {\n subscribers[key] = [wrapped]\n }\n }\n \n // Désabonnement de tous les événements pour un observateur donné\n func unsubscribe(observer: AnyObject) {\n for key in subscribers.keys {\n subscribers[key]?.removeAll(where: { subscription in\n if let sub = subscription as? Subscription {\n return sub.observer === observer\n }\n return false\n })\n }\n }\n \n // Publication d'un événement\n func publish<T: Event>(event: T) {\n let key = ObjectIdentifier(T.self)\n guard let subs = subscribers[key] else { return }\n \n for subscription in subs {\n if let sub = subscription as? Subscription {\n if let handler = sub.handler as? (T) -> Void {\n handler(event)\n }\n }\n }\n }\n}\n\n// Structure auxiliaire pour stocker les abonnements\nprivate struct Subscription {\n weak var observer: AnyObject?\n let handler: Any\n \n init(observer: AnyObject, handler: Any) {\n self.observer = observer\n self.handler = handler\n }\n}\n\n\nUtilisation de l'Event Bus
\n\n// ViewController qui s'abonne aux événements\nclass LoginViewController: UIViewController {\n \n override func viewDidLoad() {\n super.viewDidLoad()\n \n // Nous nous abonnons à l'événement