NotificationCenter è un meccanismo di sistema in iOS per inviare e ricevere notifiche tra componenti dell'applicazione senza una connessione diretta tra mittente e destinatario. Basato sul pattern Observer, NotificationCenter consente agli oggetti di iscriversi agli eventi e rispondere ad essi in modo asincrono. Secondo Apple Documentation (2025), NSNotificationCenter supporta sia l'invio sincrono di notifiche tramite post(name:object:) sia l'invio differito tramite NotificationQueue. Il centro notifiche opera all'interno di un singolo processo e non oltrepassa i confini dell'applicazione.
Punti chiave
NotificationCenter (NSNotificationCenter) è un meccanismo integrato di iOS per implementare una comunicazione debolmente accoppiata tra oggetti. Il pattern Observer consente a un oggetto (mittente) di notificare a più altri oggetti (osservatori) un evento senza un riferimento diretto ad essi. NotificationCenter opera con tre entità: Notification.Name (identificatore di notifica), Notification (contenitore con dati) e NotificationCenter (dispacciatore). Ogni applicazione ha un default center condiviso.
Notification.Name è una struttura che identifica il tipo di notifica. Creata tramite extension Name: Notification.Name(“MyNotification”). Notification è un oggetto che contiene name, object (mittente) e userInfo (dizionario con dati). Le notifiche di sistema sono dichiarate come costanti: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Le notifiche personalizzate dovrebbero essere raggruppate tramite extension per evitare collisioni di nomi. I nomi dovrebbero essere a dominio inverso.
// Definizione di notifiche personalizzate
extension Notification.Name {
static let dataDidUpdate =
Notification.Name("com.app.dataDidUpdate")
static let userLoggedOut =
Notification.Name("com.app.userLoggedOut")
}
// Invio di una notifica con dati
let userInfo: [String: Any] = [
"userId": 123,
"timestamp": Date()
]
NotificationCenter.default.post(
name: .dataDidUpdate,
object: nil,
userInfo: userInfo
)
Un osservatore si iscrive a una notifica tramite il metodo addObserver(_:selector:name:object:). Selector è il metodo che verrà chiamato alla ricezione della notifica. Il parametro object consente di filtrare le notifiche da un mittente specifico. Se object è nil, l'osservatore riceve tutte le notifiche con il nome specificato da qualsiasi mittente. Da iOS 9, addObserver non richiede la rimozione manuale per l'API block-based, ma selector-based richiede ancora removeObserver.
// Iscrizione a una notifica (basata su selettore)
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)
}
// Iscrizione a una notifica (basata su blocco, 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 memorizza una tabella di mappatura (nome → insieme di osservatori). Quando il mittente chiama post(name:object:), il centro notifiche scorre sincronamente tutti gli osservatori iscritti a quel nome e chiama i loro selettori o blocchi. Caratteristica chiave: post blocca il thread corrente fino al completamento di tutti i gestori. Se i gestori eseguono operazioni pesanti, ciò ritarda il mittente. NotificationQueue risolve questo problema differendo la consegna delle notifiche.
Il metodo post(name:object:userInfo:) invia una notifica immediatamente a tutti gli osservatori. La chiamata è sincrona — il codice dopo post viene eseguito solo dopo il completamento di tutti i gestori. L'ordine di invocazione degli osservatori non è garantito e può cambiare tra esecuzioni. Per l'elaborazione sequenziale, utilizzare NotificationQueue con coalescing. Non chiamare post all'interno di un gestore della stessa notifica — si verifica ricorsione infinita.
NotificationQueue aggiunge notifiche a una coda per la consegna asincrona. Supporta il coalescing (unione di notifiche identiche) e la selezione della coda di consegna (asap, idle, modal). Il coalescing è utile per eventi frequenti (avanzamento download) quando è necessario notificare solo con l'ultimo valore. NotificationQueue utilizza il run loop per attivarsi, quindi funziona solo in thread con run loop attivo.
// Invio differito tramite NotificationQueue
let notification = Notification(
name: .dataDidUpdate,
object: self,
userInfo: ["progress": 0.5]
)
// Coalescing: più notifiche vengono unite in una
NotificationQueue.default.enqueue(
notification,
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
// Consegna asincrona tramite DispatchQueue
DispatchQueue.main.async {
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}
iOS fornisce tre meccanismi principali per la comunicazione tra oggetti: NotificationCenter, Delegate e KVO (Key-Value Observing). Ciascuno risolve il problema della notifica ma con diversi compromessi in termini di accoppiamento, prestazioni e sicurezza dei tipi. La scelta del meccanismo dipende dalla relazione uno-a-uno o uno-a-molti e dalla necessità di trasferimento dati.
| Caratteristica | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| Accoppiamento | Debole (nome notifica) | Forte (protocollo) | Medio (chiave) |
| Relazione | Uno-a-molti | Uno-a-uno | Uno-a-molti |
| Sicurezza dei tipi | Bassa (userInfo come Dictionary) | Alta (metodi del protocollo) | Media (Any?) |
| Prestazioni | Medie (scorrimento tabella) | Alte (chiamata diretta) | Basse (NSObject) |
| Asincronia | Sincrono (post blocca) | Sincrono nel thread del mittente | Sincrono al cambiamento |
NotificationCenter è ideale per eventi a cui più componenti indipendenti devono rispondere. Esempi: modifiche alle impostazioni dell'app, logout dell'utente, ricezione di una notifica push in background. NotificationCenter è adatto anche per moduli debolmente accoppiati (la funzionalità A non deve conoscere la funzionalità B). Lo svantaggio è la mancanza di sicurezza dei tipi: le chiavi userInfo sono stringhe, non enumerazioni.
Delegate è la scelta per relazioni uno-a-uno con un contratto chiaro (tableView.delegate). Delegate è più veloce e sicuro per tipi. KVO è la scelta per osservare modifiche a una proprietà specifica del modello (isLoading, progress). KVO richiede l'ereditarietà da NSObject e può causare difficoltà di debug (stringhe magiche come chiavi). In Swift moderno, Combine e le sequenze async sostituiscono tutti e tre gli approcci.
Il metodo addObserver supporta due varianti di iscrizione: selector-based (tradizionale) e block-based (con closure). Selector-based richiede compatibilità @objc e rimozione manuale dell'osservatore. Block-based (iOS 9+) consente di utilizzare una capture list e viene gestito automaticamente dal SO quando si utilizzano blocchi senza riferimenti forti. Block-based supporta anche queue — l'osservatore riceve la notifica nella coda specificata.
Il modo tradizionale di iscrizione tramite selettore. Il metodo gestore deve essere contrassegnato con @objc e accettare una Notification opzionale. Vantaggio: può essere utilizzato da qualsiasi classe, incluso Objective-C legacy. Svantaggi: mancanza di sicurezza dei tipi del selettore, rischio di errori di battitura nel nome del selettore, removeObserver obbligatorio in deinit. Se l'osservatore viene rimosso prima dell'oggetto, il gestore non verrà chiamato.
L'API block-based accetta una closure che viene eseguita alla ricezione della notifica. Il parametro queue determina in quale coda viene eseguito il blocco — la coda principale per gli aggiornamenti dell'interfaccia o una coda in background per l'elaborazione dei dati. Il valore restituito NSObjectProtocol viene utilizzato per rimuovere l'osservatore: NotificationCenter.default.removeObserver(observer). Block-based è preferibile in Swift moderno.
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)
}
}
// Utilizzo con rimozione automatica
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() }
}
}
Le perdite di memoria sono uno dei problemi principali quando si lavora con NotificationCenter. Se un osservatore non viene rimosso prima della deallocazione, all'invio di una notifica il centro tenterà di chiamare un metodo su un oggetto già deallocato, causando EXC_BAD_ACCESS. Da iOS 9, block-based addObserver utilizza riferimenti deboli, ma selector-based richiede ancora removeObserver manuale. Buona pratica: rimuovere l'osservatore in deinit.
Selector-based: chiamare sempre NotificationCenter.default.removeObserver(self) in deinit. Se l'osservatore è iscritto a più notifiche, è possibile rimuoverle tutte in una volta (senza parametri) o una specifica per nome. Block-based: rimuovere tramite removeObserver con il token restituito da addObserver. Per block-based su iOS 9+, non si verifica perdita, ma la rimozione è comunque raccomandata per prestazioni: gli osservatori deallocati non verranno attraversati durante 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() { }
}
Il pattern Token automatizza la gestione degli osservatori. All'iscrizione, viene restituito un oggetto token (NSObjectProtocol) che rimuove automaticamente l'osservatore alla deallocazione. NotificationTokenWrapper memorizza un riferimento debole a NotificationCenter e al token dell'osservatore, chiamando removeObserver in deinit. Questo avvicina NotificationCenter all'approccio Combine, dove AnyCancellable gestisce il ciclo di vita dell'iscrizione.
Sicurezza dei thread: NotificationCenter garantisce che post possa essere chiamato da qualsiasi thread e tutti gli osservatori riceveranno la notifica nello stesso thread in cui è stato chiamato post. Questo è critico per applicazioni multithread: se una notifica viene inviata da un thread in background, anche i gestori verranno eseguiti nel thread in background. Per gli aggiornamenti dell'interfaccia, distribuire la gestione alla coda principale tramite DispatchQueue.main.async.
NotificationCenter è thread-safe per chiamate post e addObserver da thread diversi. La sincronizzazione interna utilizza blocchi, quindi post frequenti da più thread possono creare contesa. Per scenari ad alto carico (avanzamento download di 1000 file), utilizzare una coda di notifiche separata o un publisher Combine. NotificationQueue con postingStyle .now equivale a post diretto.
NotificationCenter supporta il publisher Combine tramite NotificationCenter.default.publisher(for:object:). Publisher trasforma ogni notifica in un evento Combine che può essere trasformato tramite map, filter, debounce e throttle. Questo risolve il problema della consegna sincrona: Combine elabora le notifiche in modo asincrono sul Scheduler specificato. NotificationCenter.publisher è un ponte tra il meccanismo legacy e la programmazione reattiva moderna.
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)
}
}
Domande frequenti
Sì, NotificationCenter è thread-safe per chiamate post e addObserver da qualsiasi thread. Tuttavia, i gestori vengono eseguiti nello stesso thread in cui è stato chiamato post. Per gli aggiornamenti dell'interfaccia, utilizzare queue: .main in block-based addObserver o DispatchQueue.main.async all'interno del gestore. Il publisher Combine con receive(on:) risolve anche il problema del thread.
Selector-based: crash EXC_BAD_ACCESS all'invio di una notifica dopo la deallocazione dell'osservatore. Block-based (iOS 9+): nessuna perdita grazie al riferimento debole, ma il centro notifiche continua a mantenere il blocco in memoria fino a removeObserver esplicito. Si consiglia di rimuovere sempre l'osservatore in deinit o utilizzare il pattern Token per la gestione automatica.
NotificationCenter è un meccanismo di broadcast per eventi arbitrari tra componenti non correlati. KVO osserva modifiche a una proprietà specifica di un oggetto specifico. KVO richiede l'ereditarietà da NSObject e notifica automaticamente al cambiamento della proprietà tramite setter. NotificationCenter notifica solo quando post viene chiamato esplicitamente. Per l'osservazione del modello, è preferibile KVO o Combine.
Un default center per processo applicativo. Centri aggiuntivi possono essere creati tramite NotificationCenter(), ma in pratica viene utilizzato il default condiviso. Ogni centro opera indipendentemente — post in uno non viene consegnato agli osservatori di un altro. Per l'isolamento dei moduli, utilizzare spazi dei nomi Name separati tramite nomi di notifica a dominio inverso.
Parzialmente. Combine fornisce NotificationCenter.Publisher, che avvolge NotificationCenter in un flusso reattivo. Combine risolve il problema di sincronia (tramite receive(on:)), aggiunge operatori di trasformazione e gestione automatica delle iscrizioni (AnyCancellable). Tuttavia, NotificationCenter rimane per le notifiche di sistema iOS (UIApplication, UIKeyboard) e codice legacy. Combine è un miglioramento, non una sostituzione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche