NotificationCenter — este un mecanism de sistem iOS pentru trimiterea și primirea notificărilor între componentele aplicației fără o conexiune directă între expeditor și destinatar. Bazat pe pattern-ul Observer, NotificationCenter permite obiectelor să se aboneze la evenimente și să reacționeze asincron. Conform Apple Documentation (2025), NSNotificationCenter suportă atât trimiterea sincronă a notificărilor prin post(name:object:), cât și trimiterea întârziată prin NotificationQueue. Centrul de notificări funcționează în cadrul unui singur proces și nu traversează granițele aplicațiilor.
Principalele puncte
NotificationCenter (NSNotificationCenter) — este un mecanism încorporat iOS pentru implementarea comunicării slab cuplate între obiecte. Pattern-ul Observer permite unui obiect (expeditor) să notifice multiple alte obiecte (observatori) despre producerea unui eveniment fără o referință directă la ele. NotificationCenter operează cu trei entități: Notification.Name (identificatorul notificării), Notification (container cu date) și NotificationCenter (dispecer). Fiecare aplicație are un default center partajat.
Notification.Name — este o structură care identifică tipul notificării. Se creează prin extension Name: Notification.Name("MyNotification"). Notification — este un obiect care conține name, object (expeditor) și userInfo (dicționar cu date). Notificările sistemului sunt declarate ca constante: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Notificările personalizate trebuie grupate prin extension pentru a evita coliziunile de nume. Numele trebuie să fie invers-domeniu.
// Definirea notificărilor personalizate
extension Notification.Name {
static let dataDidUpdate =
Notification.Name("com.app.dataDidUpdate")
static let userLoggedOut =
Notification.Name("com.app.userLoggedOut")
}
// Trimiterea notificării cu date
let userInfo: [String: Any] = [
"userId": 123,
"timestamp": Date()
]
NotificationCenter.default.post(
name: .dataDidUpdate,
object: nil,
userInfo: userInfo
)
Observatorul se abonează la notificare prin metoda addObserver(_:selector:name:object:). Selectorul — metoda care va fi apelată la primirea notificării. Parametrul object permite filtrarea notificărilor de la un expeditor specific. Dacă object este nil, observatorul primește toate notificările cu numele specificat de la orice expeditori. Începând cu iOS 9, addObserver nu necesită ștergere manuală pentru block-based API, dar selector-based necesită încă removeObserver.
// Abonarea la notificare (selector-based)
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)
}
// Abonarea la notificare (block-based, 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 stochează un tabel de mapare (name → set de observatori). Când expeditorul apelează post(name:object:), centrul de notificări parcurge sincron toți observatorii abonați la acest nume și le apelează selectorii sau blocurile. Caracteristica cheie: post blochează thread-ul curent până la finalizarea tuturor handler-elor. Dacă handler-ele execută operații grele, aceasta îl întârzie pe expeditor. NotificationQueue rezolvă această problemă amânând livrarea notificărilor.
Metoda post(name:object:userInfo:) trimite notificarea imediat tuturor observatorilor. Apelul este sincron — codul după post se execută numai după finalizarea tuturor handler-elor. Ordinea de apelare a observatorilor nu este garantată și se poate schimba între execuții. Pentru procesare secvențială utilizați NotificationQueue cu coalescing. Nu apelați post în interiorul handler-ului aceleiași notificări — aceasta duce la recursie infinită.
NotificationQueue adaugă notificări în coadă pentru livrare asincronă. Suportă coalescing (combinarea notificărilor identice) și selectarea cozii de livrare (asap, idle, modal). Coalescing este util pentru evenimente frecvente (progresul încărcării), când trebuie notificat doar cu ultima valoare. NotificationQueue folosește run loop pentru declanșare, deci funcționează doar în thread-uri cu run loop activ.
// Trimiterea întârziată prin NotificationQueue
let notification = Notification(
name: .dataDidUpdate,
object: self,
userInfo: ["progress": 0.5]
)
// Coalescing: mai multe notificări se combină într-una
NotificationQueue.default.enqueue(
notification,
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
// Trimiterea asincronă prin DispatchQueue
DispatchQueue.main.async {
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}
iOS oferă trei mecanisme principale de comunicare între obiecte: NotificationCenter, Delegate și KVO (Key-Value Observing). Fiecare rezolvă problema notificării, dar cu compromisuri diferite privind cuplarea, performanța și siguranța tipurilor. Alegerea mecanismului depinde de relația „unu-la-unu" sau „unu-la-mai-mulți" și de necesitatea transmiterii datelor.
| Caracteristică | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| Cuplare | Slabă (nume notificare) | Puternică (protocol) | Medie (cheie) |
| Relație | Unu-la-mai-mulți | Unu-la-unu | Unu-la-mai-mulți |
| Siguranța tipurilor | Scăzută (userInfo ca Dictionary) | Ridicată (metode protocol) | Medie (Any?) |
| Performanță | Medie (parcurgere tabelă) | Ridicată (apel direct) | Scăzută (NSObject) |
| Asincronism | Sincron (post blochează) | Sincron în thread-ul expeditorului | Sincron la modificare |
NotificationCenter este ideal pentru evenimente la care trebuie să reacționeze mai multe componente independente. Exemple: modificarea setărilor aplicației, deconectarea utilizatorului, primirea unei notificări push în fundal. NotificationCenter este potrivit și pentru module slab cuplate (funcționalitatea A nu trebuie să știe despre funcționalitatea B). Dezavantaj — lipsa siguranței tipurilor: cheile userInfo sunt șiruri, nu enum.
Delegate alegeți pentru relația unu-la-unu cu un contract clar (tableView.delegate). Delegate este mai rapid și mai sigur din punctul de vedere al tipurilor. KVO alegeți pentru observarea modificării unei proprietăți specifice a modelului (isLoading, progress). KVO necesită moștenirea NSObject și poate cauza dificultăți de depanare (șiruri magice de chei). În Swift modern, Combine și async sequences înlocuiesc toate cele trei abordări.
Metoda addObserver suportă două variante de abonare: selector-based (tradițional) și block-based (cu closure). Selector-based necesită compatibilitate @objc și ștergere manuală a observatorului. Block-based (iOS 9+) permite utilizarea capture list și este gestionat automat de OS la utilizarea blocurilor fără referințe puternice. Block-based suportă și queue — observatorul primește notificarea în coada specificată.
Metoda tradițională de abonare prin selector. Metoda handler trebuie marcată cu @objc și să primească un Notification opțional. Avantaj: posibilitatea de utilizare de către orice clasă, inclusiv legacy Objective-C. Dezavantaje: lipsa siguranței tipurilor selectorului, riscul de greșeli de tipar în numele selectorului, removeObserver obligatoriu în deinit. Dacă observatorul este șters înaintea obiectului, handler-ul nu va fi apelat.
Block-based API primește un closure care se execută la primirea notificării. Parametrul queue determină în ce coadă se execută blocul — main queue pentru actualizări UI sau background queue pentru procesarea datelor. Valoarea returnată NSObjectProtocol este utilizată pentru ștergerea observatorului: NotificationCenter.default.removeObserver(observer). Block-based este preferat în Swift modern.
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)
}
}
// Utilizarea cu ștergere automată
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() }
}
}
Scurgerile de memorie — una dintre principalele probleme la lucrul cu NotificationCenter. Dacă observatorul nu este șters înainte de dealocare, la trimiterea notificării centrul va încerca să apeleze metoda obiectului deja eliberat, ceea ce duce la EXC_BAD_ACCESS. Începând cu iOS 9, block-based addObserver folosește referințe slabe, dar selector-based necesită încă removeObserver manual. Best practice: ștergeți observatorul în deinit.
Selector-based: apelați obligatoriu NotificationCenter.default.removeObserver(self) în deinit. Dacă observatorul este abonat la mai multe notificări, le puteți șterge pe toate odată (fără parametri) sau una specifică după nume. Block-based: ștergeți prin removeObserver cu token-ul primit de la addObserver. Pentru block-based pe iOS 9+ nu apare scurgere de memorie, dar ștergerea este totuși recomandată pentru performanță: observatorii eliberați nu vor fi parcursi la 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() { }
}
Pattern-ul Token automatizează gestionarea observatorilor. La abonare se returnează un obiect-token (NSObjectProtocol), care la dealocare șterge automat observatorul. NotificationTokenWrapper stochează o referință slabă la NotificationCenter și token-ul observatorului, apelând removeObserver în deinit. Aceasta apropie NotificationCenter de abordarea Combine, unde AnyCancellable gestionează ciclul de viață al abonării.
Thread safety NotificationCenter garantează că post poate fi apelat din orice thread și toți observatorii vor primi notificarea în același thread în care a fost apelat post. Aceasta este critic pentru aplicații multi-thread: dacă notificarea a fost trimisă dintr-un thread de fundal, handler-ele se vor executa și ele în thread-ul de fundal. Pentru actualizarea UI, este necesar să dispecerizați procesarea pe main queue prin DispatchQueue.main.async.
NotificationCenter este sigur pentru thread-uri pentru apelurile post și addObserver din diferite thread-uri. Sincronizarea internă utilizează o blocare, deci post-urile frecvente din mai multe thread-uri pot crea contention. Pentru scenarii cu încărcare mare (progresul încărcării a 1000 de fișiere) utilizați o coadă separată de notificări sau Combine publisher. NotificationQueue cu postingStyle .now este echivalent cu post direct.
NotificationCenter suportă Combine publisher prin NotificationCenter.default.publisher(for:object:). Publisher transformă fiecare notificare într-un eveniment Combine care poate fi transformat prin map, filter, debounce și throttle. Aceasta rezolvă problema livrării sincrone: Combine procesează notificările asincron pe un Scheduler specificat. NotificationCenter.publisher — este o punte între mecanismul legacy și programarea reactivă modernă.
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)
}
}
Întrebări frecvente
Da, NotificationCenter este sigur pentru thread-uri pentru apelurile post și addObserver din orice thread-uri. Totuși, handler-ele se execută în același thread în care a fost apelat post. Pentru actualizarea UI utilizați queue: .main în block-based addObserver sau DispatchQueue.main.async în interiorul handler-ului. Combine publisher cu receive(on:) rezolvă și problema thread-ului.
Selector-based: crash EXC_BAD_ACCESS la trimiterea notificării după dealocarea observatorului. Block-based (iOS 9+): nu există scurgere de memorie datorită referinței slabe, dar centrul de notificări continuă să păstreze blocul în memorie până la removeObserver explicit. Se recomandă întotdeauna ștergerea observatorului în deinit sau utilizarea pattern-ului Token pentru gestionare automată.
NotificationCenter — difuzarea evenimentelor arbitrare între componente neconectate. KVO — observarea modificării unei proprietăți specifice a unui obiect specific. KVO necesită moștenirea NSObject și notifică automat la modificarea proprietății prin setter. NotificationCenter notifică doar la apelarea explicită a post. Pentru observarea modelului, KVO sau Combine sunt preferate.
Un default center per proces al aplicației. Centre suplimentare pot fi create prin NotificationCenter(), dar în practică se folosește default-ul partajat. Fiecare centru funcționează independent — post într-unul nu este livrat observatorilor altuia. Pentru izolarea modulelor, utilizați spații de nume Name separate prin nume de notificări invers-domeniu.
Parțial. Combine oferă NotificationCenter.Publisher care îmbracă NotificationCenter într-un flux reactiv. Combine rezolvă problema sincronismului (prin receive(on:)), adaugă operatori de transformare și gestionare automată a abonamentelor (AnyCancellable). Totuși, NotificationCenter rămâne pentru notificările sistemului iOS (UIApplication, UIKeyboard) și codul legacy. Combine este un strat suplimentar, nu o înlocuire.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și