NotificationCenter — essence, how it works, and notification architecture

Author: IT Sectr Published: 2026-03-18 Reading time: 10 min

NotificationCenter is a system mechanism in iOS for sending and receiving notifications between application components without a direct connection between sender and receiver. Based on the Observer pattern, NotificationCenter allows objects to subscribe to events and respond to them asynchronously. According to Apple Documentation (2025), NSNotificationCenter supports both synchronous notification posting via post(name:object:) and deferred posting via NotificationQueue. The notification center operates within a single process and does not cross application boundaries.

Key Takeaways

  • NotificationCenter — an implementation of the Observer pattern for exchanging events between iOS components.
  • addObserver subscribes an object to notifications with a specific name and sender object.
  • post(name:object:) sends a notification to all subscribed observers synchronously.
  • removeObserver must be called in deinit, otherwise a crash occurs when the notification is sent.
  • NotificationQueue allows deferring notifications for asynchronous delivery.

What is NotificationCenter?

NotificationCenter (NSNotificationCenter) is a built-in iOS mechanism for implementing loosely coupled communication between objects. The Observer pattern allows one object (sender) to notify multiple other objects (observers) about an event without a direct reference to them. NotificationCenter operates with three entities: Notification.Name (notification identifier), Notification (container with data), and NotificationCenter (dispatcher). Each application has a shared default center.

NSNotification and Notification.Name

Notification.Name is a structure that identifies the notification type. Created via extension Name: Notification.Name(“MyNotification”). Notification is an object containing name, object (sender), and userInfo (dictionary with data). System notifications are declared as constants: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Custom notifications should be grouped via extension to avoid name collisions. Names should be reverse-domain.

swift
// Defining custom notifications
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// Sending a notification with data
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

Adding an Observer (addObserver)

An observer subscribes to a notification via the addObserver(_:selector:name:object:) method. Selector is the method that will be called when the notification is received. The object parameter allows filtering notifications from a specific sender. If object is nil, the observer receives all notifications with the specified name from any sender. Since iOS 9, addObserver does not require manual removal for block-based API, but selector-based still requires removeObserver.

swift
// Subscribing to a notification (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)
}

// Subscribing to a notification (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)
}

How Does NotificationCenter Work?

NotificationCenter stores a mapping table (name → set of observers). When the sender calls post(name:object:), the notification center synchronously iterates through all observers subscribed to that name and calls their selectors or blocks. Key feature: post blocks the current thread until all handlers complete. If handlers perform heavy operations, this delays the sender. NotificationQueue solves this problem by deferring notification delivery.

Synchronous Posting (post)

The post(name:object:userInfo:) method sends a notification immediately to all observers. The call is synchronous — code after post executes only after all handlers complete. The order of observer invocation is not guaranteed and may change between launches. For sequential processing, use NotificationQueue with coalescing. Do not call post inside a handler for the same notification — this leads to infinite recursion.

Deferred Posting (NotificationQueue)

NotificationQueue adds notifications to a queue for asynchronous delivery. It supports coalescing (merging identical notifications) and delivery queue selection (asap, idle, modal). Coalescing is useful for frequent events (download progress) when you only need to notify with the latest value. NotificationQueue uses the run loop to fire, so it only works in threads with an active run loop.

swift
// Delayed posting via NotificationQueue
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing: multiple notifications are merged into one
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

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

Notification vs Delegate vs KVO

iOS provides three main mechanisms for communication between objects: NotificationCenter, Delegate, and KVO (Key-Value Observing). Each solves the notification problem but with different trade-offs in coupling, performance, and type safety. The choice of mechanism depends on the one-to-one or one-to-many relationship and the need for data transfer.

CharacteristicNotificationCenterDelegateKVO
CouplingWeak (notification name)Strong (protocol)Medium (key)
RelationshipOne-to-manyOne-to-oneOne-to-many
Type SafetyLow (userInfo as Dictionary)High (protocol methods)Medium (Any?)
PerformanceMedium (table traversal)High (direct call)Low (NSObject)
AsynchronySynchronous (post blocks)Synchronous in sender’s threadSynchronous on change

When to Choose NotificationCenter

NotificationCenter is ideal for events that multiple independent components need to respond to. Examples: app settings changes, user logout, receiving a push notification in the background. NotificationCenter is also suitable for loosely coupled modules (feature A should not know about feature B). The drawback is lack of type safety: userInfo keys are strings, not enums.

When to Choose Delegate or KVO

Delegate is the choice for one-to-one relationships with a clear contract (tableView.delegate). Delegate is faster and safer by types. KVO is the choice for observing changes to a specific model property (isLoading, progress). KVO requires inheritance from NSObject and can cause debugging difficulties (magic string keys). In modern Swift, Combine and async sequences replace all three approaches.

AddObserver: Synchronous and Asynchronous Notifications

The addObserver method supports two subscription variants: selector-based (traditional) and block-based (with closure). Selector-based requires @objc compatibility and manual observer removal. Block-based (iOS 9+) allows using a capture list and is automatically managed by the OS when using blocks without strong references. Block-based also supports queue — the observer receives the notification in the specified queue.

Selector-based addObserver

The traditional way of subscribing via selector. The handler method must be marked @objc and accept an optional Notification. Advantage: can be used by any class, including legacy Objective-C. Disadvantages: lack of type safety for the selector, risk of typos in the selector name, mandatory removeObserver in deinit. If the observer is removed before the object, the handler will not be called.

Block-based addObserver

Block-based API accepts a closure that executes when the notification is received. The queue parameter determines which queue the block runs on — main queue for UI updates or background queue for data processing. The return value NSObjectProtocol is used to remove the observer: NotificationCenter.default.removeObserver(observer). Block-based is preferred in modern 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)
    }
}

// Using with automatic removal
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() }
    }
}

Memory Management and Removing Observers

Memory leaks are one of the main issues when working with NotificationCenter. If an observer is not removed before deallocation, when a notification is sent the center will try to call a method on an already deallocated object, resulting in EXC_BAD_ACCESS. Since iOS 9, block-based addObserver uses weak references, but selector-based still requires manual removeObserver. Best practice: remove the observer in deinit.

When to Call removeObserver

Selector-based: always call NotificationCenter.default.removeObserver(self) in deinit. If the observer is subscribed to multiple notifications, you can remove all at once (without parameters) or a specific one by name. Block-based: remove via removeObserver with the token returned by addObserver. For block-based on iOS 9+, a leak does not occur, but removal is still recommended for performance: deallocated observers will not be iterated during 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() { }
}

Weak References via Token Pattern

The Token pattern automates observer management. On subscription, a token object (NSObjectProtocol) is returned, which automatically removes the observer upon deallocation. NotificationTokenWrapper stores a weak reference to NotificationCenter and the observer token, calling removeObserver in deinit. This brings NotificationCenter closer to the Combine approach, where AnyCancellable manages the subscription lifecycle.

NotificationCenter in a Multithreaded Environment

Thread safety: NotificationCenter guarantees that post can be called from any thread, and all observers will receive the notification on the same thread where post was called. This is critical for multithreaded applications: if a notification is sent from a background thread, handlers will also execute on the background thread. For UI updates, dispatch handling to the main queue via DispatchQueue.main.async.

Thread Safety of post and addObserver

NotificationCenter is thread-safe for post and addObserver calls from different threads. Internal synchronization uses locking, so frequent posts from multiple threads can create contention. For high-load scenarios (download progress of 1000 files), use a separate notification queue or Combine publisher. NotificationQueue with postingStyle .now is equivalent to direct post.

Asynchronous Delivery via Combine

NotificationCenter supports Combine publisher via NotificationCenter.default.publisher(for:object:). Publisher turns each notification into a Combine event that can be transformed through map, filter, debounce, and throttle. This solves the synchronous delivery problem: Combine processes notifications asynchronously on the specified Scheduler. NotificationCenter.publisher is a bridge between the legacy mechanism and modern reactive programming.

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

Frequently Asked Questions

Is NotificationCenter thread-safe?

Yes, NotificationCenter is thread-safe for post and addObserver calls from any thread. However, handlers execute on the same thread where post was called. For UI updates, use queue: .main in block-based addObserver or DispatchQueue.main.async inside the handler. Combine publisher with receive(on:) also solves the thread problem.

What happens if I don’t remove the observer?

Selector-based: crash EXC_BAD_ACCESS when sending a notification after observer deallocation. Block-based (iOS 9+): no leak thanks to weak reference, but the notification center continues to hold the block in memory until explicit removeObserver. It is recommended to always remove the observer in deinit or use the Token pattern for automatic management.

What is the difference between NotificationCenter and KVO?

NotificationCenter is a broadcast mechanism for arbitrary events between unrelated components. KVO observes changes to a specific property of a specific object. KVO requires NSObject inheritance and automatically notifies on property changes via setter. NotificationCenter only notifies when post is explicitly called. For model observation, KVO or Combine is preferable.

How many NotificationCenters exist in an application?

One default center per application process. Additional centers can be created via NotificationCenter(), but in practice the shared default is used. Each center operates independently — post in one does not deliver to observers of another. For module isolation, use separate Name namespaces via reverse-domain notification names.

Does Combine replace NotificationCenter?

Partially. Combine provides NotificationCenter.Publisher, which wraps NotificationCenter into a reactive stream. Combine solves the synchrony problem (via receive(on:)), adds transformation operators, and automatic subscription management (AnyCancellable). However, NotificationCenter remains for iOS system notifications (UIApplication, UIKeyboard) and legacy code. Combine is an enhancement, not a replacement.

Summary

  • NotificationCenter — an Observer pattern implementation for loosely coupled one-to-many communication in iOS.
  • post sends a notification synchronously to all observers in the current thread, blocking the sender.
  • addObserver supports selector-based (with @objc) and block-based (with capture list and queue) subscription.
  • removeObserver is mandatory in deinit for selector-based subscriptions, otherwise crash.
  • NotificationQueue provides deferred delivery with coalescing for frequent events.
  • Thread safety guarantees operation from any thread, but handlers execute in the sender’s thread.
  • Use the Token pattern or Combine publisher for safe and modern subscription management.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also