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:). Селектор — методът, който ще бъде извикан при получаване на уведомлението. Параметърът 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 съхранява таблица за съпоставяне (name → набор от наблюдатели). Когато изпращачът извика post(name:object:), центърът за уведомления синхронно обхожда всички наблюдатели, абонирани за това име, и извиква техните селектори или блокове. Ключова особеност: post блокира текущата нишка до завършване на всички handler-и. Ако handler-ите изпълняват тежки операции, това забавя изпращача. NotificationQueue решава този проблем, като отлага доставката на уведомления.

Синхронно изпращане (post)

Методът post(name:object:userInfo:) изпраща уведомлението незабавно до всички наблюдатели. Извикването е синхронно — кодът след post се изпълнява едва след завършване на всички handler-и. Редът на извикване на наблюдателите не е гарантиран и може да се променя между изпълненията. За последователна обработка използвайте NotificationQueue с coalescing. Не извиквайте post вътре в handler на същото уведомление — това води до безкрайна рекурсия.

Отложено изпращане (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 срещу Delegate срещу 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

Традиционен начин на абониране чрез селектор. Handler методът трябва да бъде маркиран с @objc и да приема опционален Notification. Предимство: възможност за използване от всеки клас, включително legacy Objective-C. Недостатъци: липса на типова безопасност на селектора, риск от правописни грешки в името на селектора, задължителен removeObserver в deinit. Ако наблюдателят бъде премахнат преди обекта, handler няма да бъде извикан.

Block-based addObserver

Block-based API приема затваряне, което се изпълнява при получаване на уведомлението. Параметър queue определя в коя опашка се изпълнява блокът — main queue за актуализации на UI или background queue за обработка на данни. Върнатата стойност 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. Best practice: премахнете наблюдателя в deinit.

Кога да извикаме removeObserver

Selector-based: задължително извикайте NotificationCenter.default.removeObserver(self) в deinit. Ако наблюдателят е абониран за няколко уведомления, можете да премахнете всички наведнъж (без параметри) или конкретно по име. Block-based: премахнете чрез removeObserver с token, получен от 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 шаблонът автоматизира управлението на наблюдатели. При абониране се връща token-обект (NSObjectProtocol), който при деалокация автоматично премахва наблюдателя. NotificationTokenWrapper съхранява слаба референция към NotificationCenter и token на наблюдателя, извиквайки removeObserver в deinit. Това доближава NotificationCenter до подхода на Combine, където AnyCancellable управлява жизнения цикъл на абонамента.

NotificationCenter в многонишкова среда

Thread safety NotificationCenter гарантира, че post може да бъде извикан от всяка нишка и всички наблюдатели ще получат уведомлението в същата нишка, в която е извикан post. Това е критично за многонишкови приложения: ако уведомлението е изпратено от фонова нишка, handler-ите също ще се изпълнят във фоновата нишка. За актуализация на UI е необходимо обработката да бъде диспечирана до main queue чрез 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 от всякакви нишки. Въпреки това handler-ите се изпълняват в същата нишка, в която е извикан post. За актуализация на UI използвайте queue: .main в block-based addObserver или DispatchQueue.main.async вътре в handler. 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 гарантира работа от всяка нишка, но handler-ите се изпълняват в нишката на изпращача.
  • Използвайте Token шаблон или Combine publisher за безопасно и модерно управление на абонаменти.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също