NotificationCenter — это системный механизм iOS для отправки и получения уведомлений между компонентами приложения без прямой связи между отправителем и получателем. Основанный на паттерне Observer, NotificationCenter позволяет объектам подписываться на события и реагировать на них асинхронно. По данным Apple Documentation (2025), NSNotificationCenter поддерживает как синхронную отправку уведомлений через post(name:object:), так и отложенную через NotificationQueue. Центр уведомлений работает в рамках одного процесса и не пересекает границы приложений.
Главное
NotificationCenter (NSNotificationCenter) — это встроенный механизм iOS для реализации слабосвязанной коммуникации между объектами. Паттерн Observer позволяет одному объекту (отправителю) уведомлять множество других объектов (наблюдателей) о наступлении события без прямой ссылки на них. NotificationCenter оперирует тремя сущностями: Notification.Name (идентификатор уведомления), Notification (контейнер с данными) и NotificationCenter (диспетчер). Каждое приложение имеет shared default center.
Notification.Name — это структура, идентифицирующая тип уведомления. Создаётся через extension Name: Notification.Name("MyNotification"). Notification — это объект, содержащий name, object (отправитель) и userInfo (словарь с данными). Системные уведомления объявлены как константы: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Кастомные уведомления следует группировать через extension для избежания коллизий имён. Имена должны быть обратно-доменными.
// Определение кастомных уведомлений
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(_:selector:name:object:). Селектор — метод, который будет вызван при получении уведомления. Параметр object позволяет фильтровать уведомления от конкретного отправителя. Если object равен nil, наблюдатель получает все уведомления с указанным именем от любых отправителей. Начиная с iOS 9, addObserver не требует ручного удаления для block-based API, но selector-based всё ещё требует removeObserver.
// Подписка на уведомление (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 хранит таблицу отображения (name → набор наблюдателей). Когда отправитель вызывает post(name:object:), центр уведомлений синхронно обходит всех наблюдателей, подписанных на это имя, и вызывает их селекторы или блоки. Ключевая особенность: post блокирует текущий поток до завершения всех обработчиков. Если обработчики выполняют тяжёлые операции, это задерживает отправителя. NotificationQueue решает эту проблему, откладывая доставку уведомлений.
Метод post(name:object:userInfo:) отправляет уведомление немедленно всем наблюдателям. Вызов синхронный — код после post выполняется только после завершения всех обработчиков. Порядок вызова наблюдателей не гарантирован и может меняться между запусками. Для последовательной обработки используйте NotificationQueue с coalescing. Не вызывайте post внутри обработчика того же уведомления — это приводит к бесконечной рекурсии.
NotificationQueue добавляет уведомления в очередь для асинхронной доставки. Поддерживает coalescing (объединение одинаковых уведомлений) и выбор очереди доставки (asap, idle, modal). Coalescing полезен для частых событий (прогресс загрузки), когда нужно уведомить только последним значением. NotificationQueue использует run loop для срабатывания, поэтому работает только в потоках с активным run loop.
// Отложенная отправка через 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)
}
iOS предоставляет три основных механизма для коммуникации между объектами: NotificationCenter, Delegate и KVO (Key-Value Observing). Каждый решает задачу уведомления, но с разными компромиссами по связанности, производительности и типовой безопасности. Выбор механизма зависит от отношения «один-к-одному» или «один-ко-многим» и необходимости передачи данных.
| Характеристика | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| Связанность | Слабая (имя уведомления) | Сильная (протокол) | Средняя (ключ) |
| Отношение | Один-ко-многим | Один-к-одному | Один-ко-многим |
| Типовая безопасность | Низкая (userInfo как Dictionary) | Высокая (методы протокола) | Средняя (Any?) |
| Производительность | Средняя (обход таблицы) | Высокая (прямой вызов) | Низкая (NSObject) |
| Асинхронность | Синхронно (post блокирует) | Синхронно в потоке отправителя | Синхронно при изменении |
NotificationCenter идеален для событий, на которые должны реагировать несколько независимых компонентов. Примеры: изменение настроек приложения, выход пользователя из аккаунта, получение push-уведомления на фоне. NotificationCenter также подходит для слабо связанных модулей (feature A не должна знать о feature B). Недостаток — нет типовой безопасности: ключи userInfo — строки, а не enum.
Delegate выбирайте для отношения один-к-одному с чётким контрактом (tableView.delegate). Delegate быстрее и безопаснее по типам. KVO выбирайте для наблюдения за изменением конкретного свойства модели (isLoading, progress). KVO требует наследования от NSObject и может вызывать трудности с отладкой (магические строки ключей). В современном Swift Combine и async sequences заменяют все три подхода.
Метод addObserver поддерживает два варианта подписки: selector-based (традиционный) и block-based (с замыканием). Selector-based требует @objc-совместимости и ручного удаления наблюдателя. Block-based (iOS 9+) позволяет использовать capture list и автоматически управляется OS при использовании блоков без сильных ссылок. Block-based также поддерживает queue — наблюдатель получает уведомление в указанной очереди.
Традиционный способ подписки через селектор. Метод-обработчик должен быть помечен @objc и принимать опциональный Notification. Преимущество: возможность использования любым классом, включая legacy Objective-C. Недостатки: отсутствие типовой безопасности селектора, риск опечаток в имени селектора, обязательный removeObserver в deinit. При удалении наблюдателя раньше объекта обработчик не будет вызван.
Block-based API принимает замыкание, которое выполняется при получении уведомления. Параметр queue определяет, в какой очереди выполняется блок — main queue для UI-обновлений или background queue для обработки данных. Возвращаемое значение NSObjectProtocol используется для удаления наблюдателя: NotificationCenter.default.removeObserver(observer). Block-based предпочтительнее в современном 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.
Selector-based: обязательно вызывайте NotificationCenter.default.removeObserver(self) в deinit. Если наблюдатель подписан на несколько уведомлений, можно удалить все сразу (без параметров) или конкретное по имени. Block-based: удалите через removeObserver с токеном, полученным от addObserver. Для block-based на iOS 9+ утечка не происходит, но удаление всё равно рекомендуется для производительности: освобождённые наблюдатели не будут обходиться при 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() { }
}
Token-паттерн автоматизирует управление наблюдателями. При подписке возвращается объект-токен (NSObjectProtocol), который при деаллокации автоматически удаляет наблюдателя. NotificationTokenWrapper хранит слабую ссылку на NotificationCenter и observer token, вызывая removeObserver в deinit. Это приближает NotificationCenter к Combine-подходу, где AnyCancellable управляет жизненным циклом подписки.
Thread safety NotificationCenter гарантирует, что post может быть вызван из любого потока, и все наблюдатели получат уведомление в том же потоке, где был вызван post. Это критически важно для многопоточных приложений: если уведомление отправлено из фонового потока, обработчики также выполнятся в фоновом потоке. Для обновления UI необходимо диспетчеризировать обработку на main queue через DispatchQueue.main.async.
NotificationCenter потокобезопасен для вызовов post и addObserver из разных потоков. Внутренняя синхронизация использует блокировку, поэтому частые post из нескольких потоков могут создавать contention. Для высоконагруженных сценариев (прогресс загрузки 1000 файлов) используйте отдельную очередь уведомлений или Combine publisher. NotificationQueue с postingStyle .now эквивалентен прямому post.
NotificationCenter поддерживает Combine publisher через NotificationCenter.default.publisher(for:object:). Publisher превращает каждое уведомление в событие Combine, которое можно трансформировать через map, filter, debounce и throttle. Это решает проблему синхронной доставки: Combine обрабатывает уведомления асинхронно на указанном Scheduler. NotificationCenter.publisher — мост между legacy-механизмом и современным реактивным программированием.
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 потокобезопасен для вызовов 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 — наблюдение за изменением конкретного свойства конкретного объекта. KVO требует наследования NSObject и автоматически уведомляет при изменении свойства через setter. NotificationCenter уведомляет только при явном вызове post. Для наблюдения за моделью предпочтительнее KVO или Combine.
Один default center на процесс приложения. Дополнительные центры можно создать через NotificationCenter(), но на практике используется общий default. Каждый центр работает независимо — post в одном не доставляется наблюдателям другого. Для изоляции модулей используйте отдельные Name-пространства через обратно-доменные имена уведомлений.
Частично. Combine предоставляет NotificationCenter.Publisher, который оборачивает NotificationCenter в реактивный поток. Combine решает проблему синхронности (через receive(on:)), добавляет операторы трансформации и автоматическое управление подписками (AnyCancellable). Однако NotificationCenter остаётся для системных уведомлений iOS (UIApplication, UIKeyboard) и legacy-кода. Combine — надстройка, а не замена.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также