NSNotificationCenter addObserver в Swift: Полное руководство с живыми примерами
Представьте, что вы пишете приложение для заказа пиццы. Пользователь нажимает «Оплатить» на экране корзины, а через несколько секунд другой экран — «Статус заказа» — должен автоматически показать, что пицца уже в пути. Как связать эти два экрана, не превращая код в спагетти из замыканий и делегатов? Ответ — NSNotificationCenter (а в современном Swift — просто NotificationCenter).
Этот паттерн — один из самых популярных в iOS-разработке. На Stack Overflow вопрос «NSNotificationCenter addObserver in Swift» набирает тысячи просмотров, потому что даже опытные разработчики иногда путаются в синтаксисе, управлении памятью и удалении наблюдателей. Давайте раз и навсегда разберемся, как правильно использовать уведомления в Swift.
Что такое NotificationCenter и зачем он нужен?
NotificationCenter — это встроенный механизм iOS, реализующий паттерн «Наблюдатель» (Observer). Он позволяет одним объектам (постерам) отправлять сообщения, а другим (наблюдателям) — подписываться на эти сообщения и реагировать на них. Главное преимущество — слабая связанность: отправитель и получатель ничего не знают друг о друге.
Когда это полезно:
- Уведомление одного экрана о действии на другом (например, корзина → статус заказа)
- Реакция на системные события (клавиатура появилась, приложение ушло в фон)
- Обновление UI после завершения фоновой загрузки данных
В старых проектах (Objective-C) использовался NSNotificationCenter, в Swift начиная с iOS 9 он называется просто NotificationCenter. В этой статье мы будем использовать современный Swift-синтаксис.
Как подписаться на уведомление: addObserver
Метод addObserver — это сердце системы. Он регистрирует объект как наблюдателя определенного уведомления. Есть два основных способа: через селектор (классика) и через блок (замыкание).
1. Метод addObserver с селектором (target-action)
Этот способ пришел еще из Objective-C. Вы указываете объект-наблюдатель, селектор (метод, который будет вызван), имя уведомления и, опционально, объект-отправителя.
import UIKit
class OrderStatusViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Подписываемся на уведомление об оплате
NotificationCenter.default.addObserver(
self,
selector: #selector(didReceivePaymentNotification(_:)),
name: Notification.Name("PaymentDidSucceed"),
object: nil
)
}
@objc func didReceivePaymentNotification(_ notification: Notification) {
print("Оплата прошла успешно! Обновляем статус заказа.")
// Здесь обновляем UI
}
// ВАЖНО: не забываем отписываться!
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Что здесь важно:
- Метод-обработчик обязан быть помечен
@objc— это требование runtime Objective-C. - Параметр
notificationтипаNotificationсодержитuserInfo— словарь с дополнительными данными. - Если передать
object: nil, наблюдатель будет получать уведомления от любого отправителя.
2. Метод addObserver с замыканием (iOS 9+)
Современный и более безопасный способ — использовать блок. Он не требует @objc и позволяет захватывать внешние переменные.
class PaymentManager {
private var observer: NSObjectProtocol?
func startObservingPayment() {
observer = NotificationCenter.default.addObserver(
forName: Notification.Name("PaymentDidSucceed"),
object: nil,
queue: .main
) { [weak self] notification in
guard let self = self else { return }
print("Получены данные: \(notification.userInfo ?? [:])")
self.showSuccessAlert()
}
}
private func showSuccessAlert() {
// Показываем alert
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Ключевые моменты:
- Метод возвращает объект типа
NSObjectProtocol— его нужно сохранить, чтобы потом отписаться. - Замыкание выполняется в указанной очереди (
.main— для обновления UI). - Обязательно используйте
[weak self], чтобы избежать цикла сильных ссылок.
Отправка уведомлений: post
Чтобы уведомление дошло до наблюдателей, его нужно отправить. Делается это через post(name:object:userInfo:).
// Где-то в PaymentViewController
let paymentData: [String: Any] = [
"orderID": 12345,
"amount": 29.99,
"currency": "USD"
]
NotificationCenter.default.post(
name: Notification.Name("PaymentDidSucceed"),
object: self,
userInfo: paymentData
)
Обратите внимание: object может быть любым объектом. Если наблюдатель указал конкретный объект при подписке, он получит уведомление только от этого отправителя.
Современный подход: Notification.Name как расширение
Хардкодить строки — плохая практика. В Swift принято создавать расширения для Notification.Name:
extension Notification.Name {
static let paymentDidSucceed = Notification.Name("PaymentDidSucceed")
static let paymentDidFail = Notification.Name("PaymentDidFail")
static let orderStatusChanged = Notification.Name("OrderStatusChanged")
}
// Использование:
NotificationCenter.default.post(name: .paymentDidSucceed, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handlePayment), name: .paymentDidSucceed, object: nil)
Так вы избегаете опечаток и получаете автодополнение в Xcode.
Удаление наблюдателей: жизненно важный шаг
Самый частый баг новичков — забыть отписаться. Если объект-наблюдатель удален из памяти, но продолжает быть зарегистрированным, при отправке уведомления произойдет креш (EXC_BAD_ACCESS).
Правило №1: Всегда вызывайте removeObserver в deinit.
Правило №2: Для селекторного метода достаточно вызвать removeObserver(self) — это удалит все подписки объекта.
Правило №3: Для блочного метода удаляйте конкретный объект:
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
Начиная с iOS 16 и Swift 5.7, появился новый способ — addObserver(with:), который возвращает токен, автоматически удаляемый при деините. Но пока большинство проектов поддерживают iOS 14+, классический подход остается актуальным.
Типичные ошибки и как их избежать
- Двойная подписка: Если выз