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 (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é.
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é.
// 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
)
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.
// 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)
}
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.
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.
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.
// 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)
}
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éristique | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| Couplage | Faible (nom de notification) | Fort (protocole) | Moyen (clé) |
| Relation | Un-à-plusieurs | Un-à-un | Un-à-plusieurs |
| Sécurité de type | Faible (userInfo comme Dictionary) | Élevée (méthodes du protocole) | Moyenne (Any?) |
| Performance | Moyenne (parcours de table) | Élevée (appel direct) | Faible (NSObject) |
| Asynchronie | Synchrone (post bloque) | Synchrone dans le thread de l'expéditeur | Synchrone lors du changement |
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.
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.
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.
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é.
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.
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() }
}
}
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.
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.
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() { }
}
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.
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.
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.
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.
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
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.
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.
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.
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é.
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é
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.
Lisez aussi