NotificationCenter — essence, principe de fonctionnement et architecture des notifications

Auteur : IT Sectr Publié le : 2026-03-18 Temps de lecture : 10 min

NotificationCenter est un mécanisme système sous iOS pour envoyer et recevoir des notifications entre les composants d'une application sans connexion directe entre l'expéditeur et le destinataire. Basé sur le pattern Observer, NotificationCenter permet aux objets de s'abonner à des événements et d'y répondre de manière asynchrone. Selon la documentation Apple (2025), NSNotificationCenter prend en charge à la fois l'envoi synchrone de notifications via post(name:object:) et l'envoi différé via NotificationQueue. Le centre de notifications opère au sein d'un seul processus et ne franchit pas les limites des applications.

Points clés

  • NotificationCenter — une implémentation du pattern Observer pour l'échange d'événements entre composants iOS.
  • addObserver abonne un objet à des notifications avec un nom spécifique et un objet expéditeur.
  • post(name:object:) envoie une notification à tous les observateurs abonnés de manière synchrone.
  • removeObserver doit être appelé dans deinit, sinon un crash se produit lors de l'envoi de la notification.
  • NotificationQueue permet de différer les notifications pour une livraison asynchrone.

Qu'est-ce que NotificationCenter ?

NotificationCenter (NSNotificationCenter) est un mécanisme intégré d'iOS pour implémenter une communication faiblement couplée entre objets. Le pattern Observer permet à un objet (expéditeur) de notifier plusieurs autres objets (observateurs) d'un événement sans référence directe à eux. NotificationCenter opère avec trois entités : Notification.Name (identifiant de notification), Notification (conteneur avec données) et NotificationCenter (répartiteur). Chaque application dispose d'un default center partagé.

NSNotification et Notification.Name

Notification.Name est une structure qui identifie le type de notification. Créée via extension Name : Notification.Name(“MyNotification”). Notification est un objet contenant name, object (expéditeur) et userInfo (dictionnaire avec données). Les notifications système sont déclarées comme des constantes : UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Les notifications personnalisées doivent être regroupées via extension pour éviter les collisions de noms. Les noms doivent être en domaine inversé.

swift
// Définition de notifications personnalisées
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// Envoi d'une notification avec données
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

Ajouter un observateur (addObserver)

Un observateur s'abonne à une notification via la méthode addObserver(_:selector:name:object:). Selector est la méthode qui sera appelée lors de la réception de la notification. Le paramètre object permet de filtrer les notifications d'un expéditeur spécifique. Si object est nil, l'observateur reçoit toutes les notifications avec le nom spécifié de n'importe quel expéditeur. Depuis iOS 9, addObserver ne nécessite pas de suppression manuelle pour l'API block-based, mais selector-based nécessite toujours removeObserver.

swift
// Abonnement à une notification (basé sur sélecteur)
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleDataUpdate),
    name: .dataDidUpdate,
    object: nil
)

@objc func handleDataUpdate(_ notification: Notification) {
    guard let userId = notification.userInfo?["userId"] as? Int else { return }
    updateUI(for: userId)
}

// Abonnement à une notification (basé sur bloc, iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
    forName: .dataDidUpdate,
    object: nil,
    queue: .main
) { [weak self] notification in
    guard let self else { return }
    self.handleNotification(notification)
}

Comment fonctionne NotificationCenter ?

NotificationCenter stocke une table de correspondance (nom → ensemble d'observateurs). Lorsque l'expéditeur appelle post(name:object:), le centre de notifications parcourt de manière synchrone tous les observateurs abonnés à ce nom et appelle leurs sélecteurs ou blocs. Caractéristique clé : post bloque le thread actuel jusqu'à ce que tous les gestionnaires soient terminés. Si les gestionnaires effectuent des opérations lourdes, cela retarde l'expéditeur. NotificationQueue résout ce problème en différant la livraison des notifications.

Envoi synchrone (post)

La méthode post(name:object:userInfo:) envoie une notification immédiatement à tous les observateurs. L'appel est synchrone — le code après post s'exécute seulement après que tous les gestionnaires sont terminés. L'ordre d'invocation des observateurs n'est pas garanti et peut changer entre les exécutions. Pour un traitement séquentiel, utilisez NotificationQueue avec coalescing. N'appelez pas post à l'intérieur d'un gestionnaire de la même notification — cela provoque une récursion infinie.

Envoi différé (NotificationQueue)

NotificationQueue ajoute des notifications à une file d'attente pour une livraison asynchrone. Il prend en charge le coalescing (fusion de notifications identiques) et la sélection de la file de livraison (asap, idle, modal). Le coalescing est utile pour les événements fréquents (progression de téléchargement) lorsque vous devez notifier uniquement avec la dernière valeur. NotificationQueue utilise le run loop pour se déclencher, donc il fonctionne uniquement dans les threads avec un run loop actif.

swift
// Envoi différé via NotificationQueue
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing : plusieurs notifications sont fusionnées en une seule
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// Livraison asynchrone via DispatchQueue
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS fournit trois mécanismes principaux pour la communication entre objets : NotificationCenter, Delegate et KVO (Key-Value Observing). Chacun résout le problème de notification mais avec différents compromis en matière de couplage, de performance et de sécurité de type. Le choix du mécanisme dépend de la relation un-à-un ou un-à-plusieurs et du besoin de transfert de données.

CaractéristiqueNotificationCenterDelegateKVO
CouplageFaible (nom de notification)Fort (protocole)Moyen (clé)
RelationUn-à-plusieursUn-à-unUn-à-plusieurs
Sécurité de typeFaible (userInfo comme Dictionary)Élevée (méthodes du protocole)Moyenne (Any?)
PerformanceMoyenne (parcours de table)Élevée (appel direct)Faible (NSObject)
AsynchronieSynchrone (post bloque)Synchrone dans le thread de l'expéditeurSynchrone lors du changement

Quand choisir NotificationCenter

NotificationCenter est idéal pour les événements auxquels plusieurs composants indépendants doivent répondre. Exemples : modifications des paramètres de l'application, déconnexion de l'utilisateur, réception d'une notification push en arrière-plan. NotificationCenter convient également aux modules faiblement couplés (la fonctionnalité A ne doit pas connaître la fonctionnalité B). L'inconvénient est le manque de sécurité de type : les clés userInfo sont des chaînes, pas des énumérations.

Quand choisir Delegate ou KVO

Delegate est le choix pour les relations un-à-un avec un contrat clair (tableView.delegate). Delegate est plus rapide et plus sûr par type. KVO est le choix pour observer les modifications d'une propriété spécifique du modèle (isLoading, progress). KVO nécessite l'héritage de NSObject et peut causer des difficultés de débogage (chaînes magiques comme clés). Dans Swift moderne, Combine et les séquences async remplacent les trois approches.

AddObserver : notifications synchrones et asynchrones

La méthode addObserver prend en charge deux variantes d'abonnement : selector-based (traditionnelle) et block-based (avec closure). Selector-based nécessite la compatibilité @objc et la suppression manuelle de l'observateur. Block-based (iOS 9+) permet d'utiliser une capture list et est géré automatiquement par l'OS lors de l'utilisation de blocs sans références fortes. Block-based prend également en charge queue — l'observateur reçoit la notification dans la file spécifiée.

addObserver basé sur sélecteur

La méthode traditionnelle d'abonnement via sélecteur. La méthode de gestion doit être marquée @objc et accepter une Notification optionnelle. Avantage : peut être utilisée par n'importe quelle classe, y compris l'Objective-C hérité. Inconvénients : manque de sécurité de type du sélecteur, risque de fautes de frappe dans le nom du sélecteur, removeObserver obligatoire dans deinit. Si l'observateur est supprimé avant l'objet, le gestionnaire ne sera pas appelé.

addObserver basé sur bloc

L'API block-based accepte une closure qui s'exécute lors de la réception de la notification. Le paramètre queue détermine dans quelle file le bloc s'exécute — la file principale pour les mises à jour de l'interface ou une file d'arrière-plan pour le traitement des données. La valeur de retour NSObjectProtocol est utilisée pour supprimer l'observateur : NotificationCenter.default.removeObserver(observer). Block-based est préférable dans Swift moderne.

swift
protocol NotificationToken {
    func dispose()
}

extension NotificationCenter {
    func observe(
        name: NSNotification.Name,
        object: Any? = nil,
        queue: OperationQueue? = .main,
        using block: @escaping (Notification) -> Void
    ) -> NotificationToken {
        let observer = addObserver(forName: name, object: object,
                                   queue: queue, using: block)
        return NotificationTokenWrapper(observer: observer, center: self)
    }
}

// Utilisation avec suppression automatique
class ViewModel {
    private var tokens: [NotificationToken] = []

    func startObserving() {
        let token = NotificationCenter.default.observe(
            name: .dataDidUpdate,
            queue: .main
        ) { [weak self] notification in
            self?.handleUpdate(notification)
        }
        tokens.append(token)
    }

    deinit {
        tokens.forEach { $0.dispose() }
    }
}

Gestion de la mémoire et suppression des observateurs

Les fuites de mémoire sont l'un des principaux problèmes lors du travail avec NotificationCenter. Si un observateur n'est pas supprimé avant la désallocation, lors de l'envoi d'une notification, le centre tentera d'appeler une méthode sur un objet déjà désalloué, entraînant EXC_BAD_ACCESS. Depuis iOS 9, block-based addObserver utilise des références faibles, mais selector-based nécessite toujours removeObserver manuel. Bonne pratique : supprimer l'observateur dans deinit.

Quand appeler removeObserver

Selector-based : appelez toujours NotificationCenter.default.removeObserver(self) dans deinit. Si l'observateur est abonné à plusieurs notifications, vous pouvez toutes les supprimer à la fois (sans paramètres) ou une spécifique par nom. Block-based : supprimez via removeObserver avec le jeton retourné par addObserver. Pour block-based sur iOS 9+, aucune fuite ne se produit, mais la suppression est toujours recommandée pour les performances : les observateurs désalloués ne seront pas parcourus lors de post.

swift
class SafeObserver {
    private var observers: [NSObjectProtocol] = []

    func addSubscriptions() {
        let token1 = NotificationCenter.default.addObserver(
            forName: .dataDidUpdate, object: nil,
            queue: .main) { [weak self] _ in
            self?.refreshData()
        }
        let token2 = NotificationCenter.default.addObserver(
            forName: .userLoggedOut, object: nil,
            queue: .main) { [weak self] _ in
            self?.logout()
        }
        observers.append(contentsOf: [token1, token2])
    }

    deinit {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
    }

    private func refreshData() { }
    private func logout() { }
}

Références faibles via le pattern Token

Le pattern Token automatise la gestion des observateurs. Lors de l'abonnement, un objet jeton (NSObjectProtocol) est retourné, qui supprime automatiquement l'observateur lors de la désallocation. NotificationTokenWrapper stocke une référence faible à NotificationCenter et au jeton d'observateur, appelant removeObserver dans deinit. Cela rapproche NotificationCenter de l'approche Combine, où AnyCancellable gère le cycle de vie de l'abonnement.

NotificationCenter dans un environnement multithread

Sécurité des threads : NotificationCenter garantit que post peut être appelé depuis n'importe quel thread, et tous les observateurs recevront la notification dans le même thread où post a été appelé. Ceci est critique pour les applications multithread : si une notification est envoyée depuis un thread d'arrière-plan, les gestionnaires s'exécuteront également dans le thread d'arrière-plan. Pour les mises à jour de l'interface, répartissez le traitement vers la file principale via DispatchQueue.main.async.

Sécurité des threads de post et addObserver

NotificationCenter est thread-safe pour les appels post et addObserver depuis différents threads. La synchronisation interne utilise des verrous, donc des appels post fréquents depuis plusieurs threads peuvent créer de la contention. Pour les scénarios à forte charge (progression de téléchargement de 1000 fichiers), utilisez une file de notifications séparée ou un publisher Combine. NotificationQueue avec postingStyle .now équivaut à un post direct.

Livraison asynchrone via Combine

NotificationCenter prend en charge le publisher Combine via NotificationCenter.default.publisher(for:object:). Publisher transforme chaque notification en un événement Combine qui peut être transformé via map, filter, debounce et throttle. Cela résout le problème de livraison synchrone : Combine traite les notifications de manière asynchrone sur le Scheduler spécifié. NotificationCenter.publisher est un pont entre le mécanisme hérité et la programmation réactive moderne.

swift
import Combine

class ReactiveViewModel {
    private var cancellables = Set<AnyCancellable>()

    func setupCombineSubscription() {
        NotificationCenter.default
            .publisher(for: .dataDidUpdate)
            .receive(on: DispatchQueue.main)
            .compactMap { $0.userInfo?["progress"] as? Float }
            .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
            .sink { [weak self] progress in
                self?.progressLabel.text = "\(Int(progress * 100))%"
            }
            .store(in: &cancellables)
    }
}

Questions fréquentes

NotificationCenter est-il thread-safe ?

Oui, NotificationCenter est thread-safe pour les appels post et addObserver depuis n'importe quel thread. Cependant, les gestionnaires s'exécutent dans le même thread où post a été appelé. Pour les mises à jour de l'interface, utilisez queue: .main dans block-based addObserver ou DispatchQueue.main.async à l'intérieur du gestionnaire. Le publisher Combine avec receive(on:) résout également le problème de thread.

Que se passe-t-il si je ne supprime pas l'observateur ?

Selector-based : crash EXC_BAD_ACCESS lors de l'envoi d'une notification après la désallocation de l'observateur. Block-based (iOS 9+) : pas de fuite grâce à la référence faible, mais le centre de notifications continue de conserver le bloc en mémoire jusqu'à removeObserver explicite. Il est recommandé de toujours supprimer l'observateur dans deinit ou d'utiliser le pattern Token pour une gestion automatique.

Quelle est la différence entre NotificationCenter et KVO ?

NotificationCenter est un mécanisme de diffusion pour des événements arbitraires entre composants non liés. KVO observe les modifications d'une propriété spécifique d'un objet spécifique. KVO nécessite l'héritage de NSObject et notifie automatiquement lors des modifications de propriété via le setter. NotificationCenter notifie uniquement lorsque post est appelé explicitement. Pour l'observation de modèle, KVO ou Combine est préférable.

Combien de NotificationCenter existent dans une application ?

Un default center par processus d'application. Des centres supplémentaires peuvent être créés via NotificationCenter(), mais dans la pratique, le default partagé est utilisé. Chaque centre fonctionne indépendamment — post dans l'un n'est pas livré aux observateurs d'un autre. Pour l'isolation des modules, utilisez des espaces de noms Name séparés via des noms de notification en domaine inversé.

Combine remplace-t-il NotificationCenter ?

Partiellement. Combine fournit NotificationCenter.Publisher, qui encapsule NotificationCenter dans un flux réactif. Combine résout le problème de synchronie (via receive(on:)), ajoute des opérateurs de transformation et une gestion automatique des abonnements (AnyCancellable). Cependant, NotificationCenter reste pour les notifications système iOS (UIApplication, UIKeyboard) et le code hérité. Combine est une amélioration, pas un remplacement.

Résumé

  • NotificationCenter — une implémentation du pattern Observer pour la communication faiblement couplée un-à-plusieurs dans iOS.
  • post envoie une notification de manière synchrone à tous les observateurs dans le thread actuel, bloquant l'expéditeur.
  • addObserver prend en charge l'abonnement selector-based (avec @objc) et block-based (avec capture list et queue).
  • removeObserver est obligatoire dans deinit pour les abonnements selector-based, sinon crash.
  • NotificationQueue fournit une livraison différée avec coalescing pour les événements fréquents.
  • Sécurité des threads garantit le fonctionnement depuis n'importe quel thread, mais les gestionnaires s'exécutent dans le thread de l'expéditeur.
  • Utilisez le pattern Token ou le publisher Combine pour une gestion d'abonnement sûre et moderne.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi