NotificationCenter — суть, принцип роботи та архітектура сповіщень

Автор: IT Sectr Опубліковано: 2026-03-18 Час читання: 10 хв

NotificationCenter — це системний механізм iOS для надсилання та отримання сповіщень між компонентами застосунку без прямого зв'язку між відправником та отримувачем. Заснований на паттерні Observer, NotificationCenter дозволяє об'єктам підписуватися на події та реагувати на них асинхронно. За даними Apple Documentation (2025), NSNotificationCenter підтримує як синхронне надсилання сповіщень через post(name:object:), так і відкладене через NotificationQueue. Центр сповіщень працює в межах одного процесу та не перетинає меж застосунків.

Головне

  • NotificationCenter — реалізація паттерну Observer для обміну подіями між компонентами iOS.
  • addObserver підписує об'єкт на сповіщення з певним іменем та об'єктом-відправником.
  • post(name:object:) надсилає сповіщення всім підписаним спостерігачам синхронно.
  • removeObserver обов'язковий для виклику в deinit, інакше виникає crash при надсиланні сповіщення.
  • NotificationQueue дозволяє відкладати сповіщення для асинхронної доставки.

Що таке NotificationCenter?

NotificationCenter (NSNotificationCenter) — це вбудований механізм iOS для реалізації слабкозв'язаної комунікації між об'єктами. Паттерн Observer дозволяє одному об'єкту (відправнику) сповіщати безліч інших об'єктів (спостерігачів) про настання події без прямого посилання на них. NotificationCenter оперує трьома сутностями: Notification.Name (ідентифікатор сповіщення), Notification (контейнер з даними) та NotificationCenter (диспетчер). Кожен застосунок має спільний default center.

NSNotification та Notification.Name

Notification.Name — це структура, яка ідентифікує тип сповіщення. Створюється через extension Name: Notification.Name(“MyNotification”). Notification — це об'єкт, що містить name, object (відправник) та userInfo (словник з даними). Системні сповіщення оголошені як константи: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Користувацькі сповіщення слід групувати через extension для уникнення колізій імен. Імена мають бути зворотно-доменними.

swift
// Визначення кастомних сповіщень
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// Надсилання сповіщення з даними
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

Додавання спостерігача (addObserver)

Спостерігач підписується на сповіщення через метод addObserver(_:selector:name:object:). Selector — метод, який буде викликано при отриманні сповіщення. Параметр object дозволяє фільтрувати сповіщення від конкретного відправника. Якщо object дорівнює nil, спостерігач отримує всі сповіщення з вказаним іменем від будь-яких відправників. Починаючи з iOS 9, addObserver не вимагає ручного видалення для block-based API, але selector-based все ще потребує removeObserver.

swift
// Підписка на сповіщення (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)
}

// Підписка на сповіщення (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?

NotificationCenter зберігає таблицю відображення (ім'я → набір спостерігачів). Коли відправник викликає post(name:object:), центр сповіщень синхронно обходить всіх спостерігачів, підписаних на це ім'я, та викликає їх селектори або блоки. Ключова особливість: post блокує поточний потік до завершення всіх обробників. Якщо обробники виконують важкі операції, це затримує відправника. NotificationQueue вирішує цю проблему, відкладаючи доставку сповіщень.

Синхронне надсилання (post)

Метод post(name:object:userInfo:) надсилає сповіщення негайно всім спостерігачам. Виклик синхронний — код після post виконується тільки після завершення всіх обробників. Порядок виклику спостерігачів не гарантований і може змінюватися між запусками. Для послідовної обробки використовуйте NotificationQueue з coalescing. Не викликайте post всередині обробника того ж сповіщення — це призводить до нескінченної рекурсії.

Відкладене надсилання (NotificationQueue)

NotificationQueue додає сповіщення до черги для асинхронної доставки. Підтримує coalescing (об'єднання однакових сповіщень) та вибір черги доставки (asap, idle, modal). Coalescing корисний для частих подій (прогрес завантаження), коли потрібно сповістити лише останнім значенням. NotificationQueue використовує run loop для спрацьовування, тому працює тільки в потоках з активним run loop.

swift
// Відкладене надсилання через NotificationQueue
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing: кілька сповіщень об'єднуються в одне
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// Асинхронна доставка через DispatchQueue
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS надає три основні механізми для комунікації між об'єктами: NotificationCenter, Delegate та KVO (Key-Value Observing). Кожен вирішує задачу сповіщення, але з різними компромісами щодо зв'язаності, продуктивності та типової безпеки. Вибір механізму залежить від відношення «один-до-одного» чи «один-до-багатьох» та необхідності передачі даних.

ХарактеристикаNotificationCenterDelegateKVO
Зв'язаністьСлабка (ім'я сповіщення)Сильна (протокол)Середня (ключ)
ВідношенняОдин-до-багатьохОдин-до-одногоОдин-до-багатьох
Типова безпекаНизька (userInfo як Dictionary)Висока (методи протоколу)Середня (Any?)
ПродуктивністьСередня (обхід таблиці)Висока (прямий виклик)Низька (NSObject)
АсинхронністьСинхронно (post блокує)Синхронно в потоці відправникаСинхронно при зміні

Коли вибирати NotificationCenter

NotificationCenter ідеальний для подій, на які повинні реагувати кілька незалежних компонентів. Приклади: зміна налаштувань застосунку, вихід користувача з облікового запису, отримання push-сповіщення у фоновому режимі. NotificationCenter також підходить для слабко зв'язаних модулів (функція A не повинна знати про функцію B). Недолік — відсутність типової безпеки: ключі userInfo — рядки, а не enum.

Коли вибирати Delegate або KVO

Delegate обирайте для відношення один-до-одного з чітким контрактом (tableView.delegate). Delegate швидший і безпечніший за типами. KVO обирайте для спостереження за зміною конкретної властивості моделі (isLoading, progress). KVO вимагає успадкування від NSObject і може викликати труднощі з налагодженням (магічні рядки ключів). У сучасному Swift Combine та async sequences замінюють усі три підходи.

AddObserver: синхронні та асинхронні сповіщення

Метод addObserver підтримує два варіанти підписки: selector-based (традиційний) та block-based (із замиканням). Selector-based вимагає @objc-сумісності та ручного видалення спостерігача. Block-based (iOS 9+) дозволяє використовувати capture list та автоматично керується OS при використанні блоків без сильних посилань. Block-based також підтримує queue — спостерігач отримує сповіщення у вказаній черзі.

Selector-based addObserver

Традиційний спосіб підписки через селектор. Метод-обробник повинен бути позначений @objc та приймати опціональний Notification. Перевага: можливість використання будь-яким класом, включаючи legacy Objective-C. Недоліки: відсутність типової безпеки селектора, ризик помилок у назві селектора, обов'язковий removeObserver в deinit. При видаленні спостерігача раніше об'єкта обробник не буде викликаний.

Block-based addObserver

Block-based API приймає замикання, яке виконується при отриманні сповіщення. Параметр queue визначає, в якій черзі виконується блок — головній черзі для оновлень UI або фоновій черзі для обробки даних. Значення, що повертається NSObjectProtocol, використовується для видалення спостерігача: NotificationCenter.default.removeObserver(observer). Block-based переважніший у сучасному 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)
    }
}

// Використання з автоматичним видаленням
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() }
    }
}

Управління пам'яттю та видалення спостерігачів

Витоки пам'яті — одна з головних проблем при роботі з NotificationCenter. Якщо спостерігач не видалений до деалокації, при надсиланні сповіщення центр спробує викликати метод вже звільненого об'єкта, що призведе до EXC_BAD_ACCESS. Починаючи з iOS 9, block-based addObserver використовує слабкі посилання, але selector-based все ще потребує ручного removeObserver. Найкраща практика: видаляти спостерігача в deinit.

Коли викликати removeObserver

Selector-based: обов'язково викликайте NotificationCenter.default.removeObserver(self) в deinit. Якщо спостерігач підписаний на кілька сповіщень, можна видалити всі одразу (без параметрів) або конкретне за іменем. Block-based: видаліть через removeObserver з токеном, отриманим від addObserver. Для block-based на iOS 9+ витік не відбувається, але видалення все одно рекомендується для продуктивності: звільнені спостерігачі не будуть обходитися при 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() { }
}

Слабкі посилання через Token-паттерн

Token-паттерн автоматизує управління спостерігачами. При підписці повертається об'єкт-токен (NSObjectProtocol), який при деалокації автоматично видаляє спостерігача. NotificationTokenWrapper зберігає слабке посилання на NotificationCenter та токен спостерігача, викликаючи removeObserver в deinit. Це наближає NotificationCenter до Combine-підходу, де AnyCancellable керує життєвим циклом підписки.

NotificationCenter в багатопотоковому середовищі

Thread safety: NotificationCenter гарантує, що post може бути викликаний з будь-якого потоку, і всі спостерігачі отримають сповіщення в тому ж потоці, де був викликаний post. Це критично важливо для багатопотокових застосунків: якщо сповіщення надіслано з фонового потоку, обробники також виконаються в фоновому потоці. Для оновлення UI необхідно диспетчеризувати обробку на головну чергу через DispatchQueue.main.async.

Потокобезпечність post та addObserver

NotificationCenter потокобезпечний для викликів post та addObserver з різних потоків. Внутрішня синхронізація використовує блокування, тому часті post з кількох потоків можуть створювати contention. Для високонавантажених сценаріїв (прогрес завантаження 1000 файлів) використовуйте окрему чергу сповіщень або Combine publisher. NotificationQueue з postingStyle .now еквівалентний прямому post.

Асинхронна доставка через Combine

NotificationCenter підтримує Combine publisher через NotificationCenter.default.publisher(for:object:). Publisher перетворює кожне сповіщення на подію Combine, яку можна трансформувати через map, filter, debounce та throttle. Це вирішує проблему синхронної доставки: Combine обробляє сповіщення асинхронно на вказаному Scheduler. NotificationCenter.publisher — міст між legacy-механізмом та сучасним реактивним програмуванням.

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

Часті запитання

Чи потокобезпечний NotificationCenter?

Так, NotificationCenter потокобезпечний для викликів post та addObserver з будь-яких потоків. Однак обробники виконуються в тому ж потоці, де був викликаний post. Для оновлення UI використовуйте queue: .main в block-based addObserver або DispatchQueue.main.async всередині обробника. Combine publisher з receive(on:) також вирішує проблему потоку.

Що станеться, якщо не видалити спостерігача?

Selector-based: crash EXC_BAD_ACCESS при надсиланні сповіщення після деалокації спостерігача. Block-based (iOS 9+): витоку немає завдяки слабкому посиланню, але центр сповіщень продовжує зберігати блок у пам'яті до явного removeObserver. Рекомендується завжди видаляти спостерігача в deinit або використовувати Token-паттерн для автоматичного керування.

У чому різниця між NotificationCenter та KVO?

NotificationCenter — широкомовна розсилка довільних подій між непов'язаними компонентами. KVO — спостереження за зміною конкретної властивості конкретного об'єкта. KVO вимагає успадкування NSObject та автоматично сповіщає при зміні властивості через setter. NotificationCenter сповіщає тільки при явному виклику post. Для спостереження за моделлю переважніший KVO або Combine.

Скільки NotificationCenter існує в застосунку?

Один default center на процес застосунку. Додаткові центри можна створити через NotificationCenter(), але на практиці використовується спільний default. Кожен центр працює незалежно — post в одному не доставляється спостерігачам іншого. Для ізоляції модулів використовуйте окремі Name-простори через зворотно-доменні імена сповіщень.

Чи замінює Combine NotificationCenter?

Частково. Combine надає NotificationCenter.Publisher, який обертає NotificationCenter у реактивний потік. Combine вирішує проблему синхронності (через receive(on:)), додає оператори трансформації та автоматичне керування підписками (AnyCancellable). Однак NotificationCenter залишається для системних сповіщень iOS (UIApplication, UIKeyboard) та legacy-коду. Combine — надбудова, а не заміна.

Підсумки

  • NotificationCenter — реалізація Observer-паттерну для слабкозв'язаної комунікації «один-до-багатьох» в iOS.
  • post надсилає сповіщення синхронно всім спостерігачам у поточному потоці, блокуючи відправника.
  • addObserver підтримує selector-based (з @objc) та block-based (з capture list та queue) підписку.
  • removeObserver обов'язковий в deinit для selector-based підписок, інакше crash.
  • NotificationQueue забезпечує відкладену доставку з coalescing для частих подій.
  • Thread safety гарантує роботу з будь-якого потоку, але обробники виконуються в потоці відправника.
  • Використовуйте Token-паттерн або Combine publisher для безпечного та сучасного керування підписками.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також