NotificationCenter — istota, zasada działania i architektura powiadomień

Autor: IT Sectr Opublikowano: 2026-03-18 Czas czytania: 10 min

NotificationCenter — to systemowy mechanizm iOS do wysyłania i odbierania powiadomień między komponentami aplikacji bez bezpośredniego połączenia między nadawcą a odbiorcą. Oparty na wzorcu Observer, NotificationCenter pozwala obiektom subskrybować zdarzenia i reagować na nie asynchronicznie. Według Apple Documentation (2025), NSNotificationCenter obsługuje zarówno synchroniczne wysyłanie powiadomień przez post(name:object:), jak i opóźnione przez NotificationQueue. Centrum powiadomień działa w ramach jednego procesu i nie przekracza granic aplikacji.

Najważniejsze

  • NotificationCenter — implementacja wzorca Observer do wymiany zdarzeń między komponentami iOS.
  • addObserver subskrybuje obiekt na powiadomienia o określonej nazwie i obiekcie nadawcy.
  • post(name:object:) wysyła powiadomienie do wszystkich subskrybujących obserwatorów synchronicznie.
  • removeObserver jest obowiązkowy w deinit, w przeciwnym razie wystąpi crash przy wysyłaniu powiadomienia.
  • NotificationQueue pozwala opóźniać powiadomienia dla asynchronicznej dostawy.

Czym jest NotificationCenter?

NotificationCenter (NSNotificationCenter) — to wbudowany mechanizm iOS do realizacji słabo powiązanej komunikacji między obiektami. Wzorzec Observer pozwala jednemu obiektowi (nadawcy) powiadamiać wiele innych obiektów (obserwatorów) o wystąpieniu zdarzenia bez bezpośredniego odwołania do nich. NotificationCenter operuje na trzech bytach: Notification.Name (identyfikator powiadomienia), Notification (kontener z danymi) i NotificationCenter (dyspozytor). Każda aplikacja ma współdzielone default center.

NSNotification i Notification.Name

Notification.Name — to struktura identyfikująca typ powiadomienia. Tworzona przez extension Name: Notification.Name("MyNotification"). Notification — to obiekt zawierający name, object (nadawca) i userInfo (słownik z danymi). Systemowe powiadomienia są zadeklarowane jako stałe: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Niestandardowe powiadomienia należy grupować przez extension, aby uniknąć kolizji nazw. Nazwy powinny być odwrotnie domenowe.

swift
// Definiowanie niestandardowych powiadomień
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// Wysyłanie powiadomienia z danymi
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

Dodawanie obserwatora (addObserver)

Obserwator subskrybuje powiadomienie przez metodę addObserver(_:selector:name:object:). Selektor — metoda, która zostanie wywołana po otrzymaniu powiadomienia. Parametr object pozwala filtrować powiadomienia od konkretnego nadawcy. Jeśli object ma wartość nil, obserwator otrzymuje wszystkie powiadomienia o podanej nazwie od dowolnych nadawców. Od iOS 9 addObserver nie wymaga ręcznego usuwania dla block-based API, ale selector-based nadal wymaga removeObserver.

swift
// Subskrypcja powiadomienia (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)
}

// Subskrypcja powiadomienia (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)
}

Jak działa NotificationCenter?

NotificationCenter przechowuje tabelę mapowania (name → zestaw obserwatorów). Gdy nadawca wywołuje post(name:object:), centrum powiadomień synchronicznie przegląda wszystkich obserwatorów subskrybujących tę nazwę i wywołuje ich selektory lub bloki. Kluczowa cecha: post blokuje bieżący wątek do czasu zakończenia wszystkich handlerów. Jeśli handlery wykonują ciężkie operacje, opóźnia to nadawcę. NotificationQueue rozwiązuje ten problem, opóźniając dostarczanie powiadomień.

Synchroniczne wysyłanie (post)

Metoda post(name:object:userInfo:) wysyła powiadomienie natychmiast do wszystkich obserwatorów. Wywołanie jest synchroniczne — kod po post wykonuje się dopiero po zakończeniu wszystkich handlerów. Kolejność wywołania obserwatorów nie jest gwarantowana i może się zmieniać między uruchomieniami. Do sekwencyjnego przetwarzania używaj NotificationQueue z coalescing. Nie wywołuj post wewnątrz handlera tego samego powiadomienia — prowadzi to do nieskończonej rekurencji.

Opóźnione wysyłanie (NotificationQueue)

NotificationQueue dodaje powiadomienia do kolejki w celu asynchronicznej dostawy. Obsługuje coalescing (łączenie identycznych powiadomień) i wybór kolejki dostawy (asap, idle, modal). Coalescing jest przydatny w przypadku częstych zdarzeń (postęp ładowania), gdy trzeba powiadomić tylko ostatnią wartością. NotificationQueue używa run loop do wyzwalania, więc działa tylko w wątkach z aktywnym run loop.

swift
// Opóźnione wysyłanie przez NotificationQueue
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing: wiele powiadomień łączy się w jedno
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// Asynchroniczne wysyłanie przez DispatchQueue
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS udostępnia trzy główne mechanizmy komunikacji między obiektami: NotificationCenter, Delegate i KVO (Key-Value Observing). Każdy rozwiązuje zadanie powiadamiania, ale z różnymi kompromisami dotyczącymi powiązania, wydajności i bezpieczeństwa typów. Wybór mechanizmu zależy od relacji „jeden-do-jednego” lub „jeden-do-wielu” oraz potrzeby przesyłania danych.

CechaNotificationCenterDelegateKVO
PowiązanieSłabe (nazwa powiadomienia)Silne (protokół)Średnie (klucz)
RelacjaJeden-do-wieluJeden-do-jednegoJeden-do-wielu
Bezpieczeństwo typówNiskie (userInfo jako Dictionary)Wysokie (metody protokołu)Średnie (Any?)
WydajnośćŚrednia (przeglądanie tabeli)Wysoka (bezpośrednie wywołanie)Niska (NSObject)
AsynchronicznośćSynchronicznie (post blokuje)Synchronicznie w wątku nadawcySynchronicznie przy zmianie

Kiedy wybierać NotificationCenter

NotificationCenter jest idealny dla zdarzeń, na które musi reagować kilka niezależnych komponentów. Przykłady: zmiana ustawień aplikacji, wylogowanie użytkownika, otrzymanie push powiadomienia w tle. NotificationCenter nadaje się również dla słabo powiązanych modułów (funkcja A nie musi wiedzieć o funkcji B). Wadą jest brak bezpieczeństwa typów: klucze userInfo to ciągi znaków, a nie enum.

Kiedy wybierać Delegate lub KVO

Delegate wybieraj dla relacji jeden-do-jednego z jasnym kontraktem (tableView.delegate). Delegate jest szybszy i bezpieczniejszy typowo. KVO wybieraj do obserwowania zmiany konkretnej właściwości modelu (isLoading, progress). KVO wymaga dziedziczenia po NSObject i może sprawiać trudności w debugowaniu (magiczne ciągi kluczy). We współczesnym Swift Combine i async sequences zastępują wszystkie trzy podejścia.

AddObserver: synchroniczne i asynchroniczne powiadomienia

Metoda addObserver obsługuje dwa warianty subskrypcji: selector-based (tradycyjny) i block-based (z domknięciem). Selector-based wymaga zgodności z @objc i ręcznego usuwania obserwatora. Block-based (iOS 9+) pozwala używać capture list i jest automatycznie zarządzany przez OS przy użyciu bloków bez silnych referencji. Block-based obsługuje również queue — obserwator otrzymuje powiadomienie w określonej kolejce.

Selector-based addObserver

Tradycyjny sposób subskrypcji przez selektor. Metoda-handler musi być oznaczona @objc i przyjmować opcjonalny Notification. Zaleta: możliwość użycia przez dowolną klasę, w tym legacy Objective-C. Wady: brak bezpieczeństwa typów selektora, ryzyko literówek w nazwie selektora, obowiązkowy removeObserver w deinit. Po usunięciu obserwatora wcześniej niż obiekt, handler nie zostanie wywołany.

Block-based addObserver

Block-based API przyjmuje domknięcie, które wykonuje się po otrzymaniu powiadomienia. Parametr queue określa, w której kolejce wykonuje się blok — main queue dla aktualizacji UI lub background queue do przetwarzania danych. Zwracana wartość NSObjectProtocol jest używana do usunięcia obserwatora: NotificationCenter.default.removeObserver(observer). Block-based jest preferowany we współczesnym Swift.

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)
    }
}

// Użycie z automatycznym usuwaniem
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() }
    }
}

Zarządzanie pamięcią i usuwanie obserwatorów

Wycieki pamięci — jeden z głównych problemów podczas pracy z NotificationCenter. Jeśli obserwator nie zostanie usunięty przed dealokacją, przy wysyłaniu powiadomienia centrum spróbuje wywołać metodę już zwolnionego obiektu, co doprowadzi do EXC_BAD_ACCESS. Od iOS 9 block-based addObserver używa słabych referencji, ale selector-based nadal wymaga ręcznego removeObserver. Best practice: usuwać obserwatora w deinit.

Kiedy wywoływać removeObserver

Selector-based: obowiązkowo wywołuj NotificationCenter.default.removeObserver(self) w deinit. Jeśli obserwator subskrybuje kilka powiadomień, można usunąć wszystkie naraz (bez parametrów) lub konkretne po nazwie. Block-based: usuń przez removeObserver z tokenem otrzymanym z addObserver. Dla block-based na iOS 9+ wyciek nie występuje, ale usunięcie jest nadal zalecane dla wydajności: zwolnieni obserwatorzy nie będą przeglądani przy 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() { }
}

Słabe referencje przez Token-pattern

Token-pattern automatyzuje zarządzanie obserwatorami. Przy subskrypcji zwracany jest obiekt-token (NSObjectProtocol), który przy dealokacji automatycznie usuwa obserwatora. NotificationTokenWrapper przechowuje słabą referencję do NotificationCenter i token obserwatora, wywołując removeObserver w deinit. To przybliża NotificationCenter do podejścia Combine, gdzie AnyCancellable zarządza cyklem życia subskrypcji.

NotificationCenter w środowisku wielowątkowym

Thread safety NotificationCenter gwarantuje, że post może być wywołany z dowolnego wątku, a wszyscy obserwatorzy otrzymają powiadomienie w tym samym wątku, w którym został wywołany post. Jest to krytyczne dla aplikacji wielowątkowych: jeśli powiadomienie zostało wysłane z wątku tła, handlery również wykonają się w wątku tła. Do aktualizacji UI należy zdysponować przetwarzanie na main queue przez DispatchQueue.main.async.

Bezpieczeństwo wątku post i addObserver

NotificationCenter jest bezpieczny wątkowo dla wywołań post i addObserver z różnych wątków. Wewnętrzna synchronizacja używa blokady, więc częste post z wielu wątków mogą tworzyć contention. Dla scenariuszy o wysokim obciążeniu (postęp ładowania 1000 plików) używaj osobnej kolejki powiadomień lub Combine publisher. NotificationQueue z postingStyle .now jest równoważny bezpośredniemu post.

Asynchroniczna dostawa przez Combine

NotificationCenter obsługuje Combine publisher przez NotificationCenter.default.publisher(for:object:). Publisher zamienia każde powiadomienie w zdarzenie Combine, które można transformować przez map, filter, debounce i throttle. To rozwiązuje problem synchronicznej dostawy: Combine przetwarza powiadomienia asynchronicznie na określonym Scheduler. NotificationCenter.publisher — to most między legacy mechanizmem a nowoczesnym programowaniem reaktywnym.

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)
    }
}

Często zadawane pytania

Czy NotificationCenter jest bezpieczny wątkowo?

Tak, NotificationCenter jest bezpieczny wątkowo dla wywołań post i addObserver z dowolnych wątków. Jednak handlery wykonują się w tym samym wątku, w którym został wywołany post. Do aktualizacji UI używaj queue: .main w block-based addObserver lub DispatchQueue.main.async wewnątrz handlera. Combine publisher z receive(on:) również rozwiązuje problem wątku.

Co się stanie, jeśli nie usunę obserwatora?

Selector-based: crash EXC_BAD_ACCESS przy wysyłaniu powiadomienia po dealokacji obserwatora. Block-based (iOS 9+): nie ma wycieku dzięki słabej referencji, ale centrum powiadomień nadal przechowuje blok w pamięci do jawnego removeObserver. Zaleca się zawsze usuwać obserwatora w deinit lub używać Token-pattern do automatycznego zarządzania.

Jaka jest różnica między NotificationCenter a KVO?

NotificationCenter — rozgłaszanie dowolnych zdarzeń między niepowiązanymi komponentami. KVO — obserwowanie zmiany konkretnej właściwości konkretnego obiektu. KVO wymaga dziedziczenia NSObject i automatycznie powiadamia przy zmianie właściwości przez setter. NotificationCenter powiadamia tylko przy jawnym wywołaniu post. Do obserwowania modelu preferowany jest KVO lub Combine.

Ile NotificationCenter istnieje w aplikacji?

Jedno default center na proces aplikacji. Dodatkowe centra można utworzyć przez NotificationCenter(), ale w praktyce używa się wspólnego default. Każde centrum działa niezależnie — post w jednym nie jest dostarczany obserwatorom drugiego. Do izolacji modułów używaj oddzielnych przestrzeni nazw Name przez odwrotnie domenowe nazwy powiadomień.

Czy Combine zastępuje NotificationCenter?

Częściowo. Combine udostępnia NotificationCenter.Publisher, który opakowuje NotificationCenter w strumień reaktywny. Combine rozwiązuje problem synchroniczności (przez receive(on:)), dodaje operatory transformacji i automatyczne zarządzanie subskrypcjami (AnyCancellable). Jednak NotificationCenter pozostaje dla systemowych powiadomień iOS (UIApplication, UIKeyboard) i legacy kodu. Combine to nadbudowa, a nie zamiennik.

Podsumowanie

  • NotificationCenter — implementacja wzorca Observer dla słabo powiązanej komunikacji „jeden-do-wielu" w iOS.
  • post wysyła powiadomienie synchronicznie do wszystkich obserwatorów w bieżącym wątku, blokując nadawcę.
  • addObserver obsługuje subskrypcję selector-based (z @objc) i block-based (z capture list i queue).
  • removeObserver jest obowiązkowy w deinit dla subskrypcji selector-based, w przeciwnym razie crash.
  • NotificationQueue zapewnia opóźnioną dostawę z coalescing dla częstych zdarzeń.
  • Thread safety gwarantuje pracę z dowolnego wątku, ale handlery wykonują się w wątku nadawcy.
  • Używaj Token-pattern lub Combine publisher do bezpiecznego i nowoczesnego zarządzania subskrypcjami.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również