Как правильно использовать NSNotificationCenter (addObserver) в Swift: разбираем синтаксис и отписку
Правильный синтаксис NSNotificationCenter в Swift
Вы на правильном пути, но давайте разберём всё по порядку. В современном Swift (начиная с версии 2.2) для селекторов используется конструкция #selector, а имя уведомления лучше хранить как константу типа Notification.Name.
1. Объявление уведомления
Сначала определите имя уведомления как расширение Notification.Name:
extension Notification.Name {
static let myCustomNotification = Notification.Name("MyCustomNotification")
}
2. Добавление наблюдателя
Используйте #selector для указания метода-обработчика:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: .myCustomNotification,
object: nil
)
}
3. Метод-обработчик
Метод должен принимать один параметр типа Notification (или не принимать ничего, если данные не нужны):
@objc func handleNotification(_ notification: Notification) {
// Получаем данные из userInfo, если они есть
if let userInfo = notification.userInfo,
let message = userInfo["message"] as? String {
print("Получено уведомление: \(message)")
}
// Также можно получить объект, который отправил уведомление
if let sender = notification.object as? MyClass {
print("Отправитель: \(sender)")
}
}
Обратите внимание на ключевое слово @objc — оно необходимо, чтобы метод был виден Objective-C runtime, который использует NotificationCenter.
4. Отправка уведомления
Где-то в другом месте (например, при нажатии кнопки):
let userInfo: [String: Any] = ["message": "Привет из другого контроллера!"]
NotificationCenter.default.post(
name: .myCustomNotification,
object: self,
userInfo: userInfo
)
5. Обязательная отписка
Если вы используете addObserver вручную, вы обязаны удалить наблюдателя, иначе при деинициализации объекта приложение упадёт с ошибкой. Лучшее место — deinit:
deinit {
NotificationCenter.default.removeObserver(self)
}
Также можно удалить конкретное уведомление:
deinit {
NotificationCenter.default.removeObserver(
self,
name: .myCustomNotification,
object: nil
)
}
6. Альтернатива: блоки (closures) — современный подход
Начиная с iOS 10, можно использовать метод с блоком, который возвращает токен. Это удобнее, так как отписка происходит через токен:
var notificationToken: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
notificationToken = NotificationCenter.default.addObserver(
forName: .myCustomNotification,
object: nil,
queue: .main
) { [weak self] notification in
guard let self = self else { return }
if let message = notification.userInfo?["message"] as? String {
self.updateUI(with: message)
}
}
}
deinit {
if let token = notificationToken {
NotificationCenter.default.removeObserver(token)
}
}
Обратите внимание на [weak self] — это предотвращает циклические ссылки.
Полный рабочий пример
Вот минимальный, но полный пример двух контроллеров, общающихся через уведомления:
// MARK: - Определение уведомления
extension Notification.Name {
static let dataUpdated = Notification.Name("DataUpdated")
}
// MARK: - Отправитель
class SenderViewController: UIViewController {
@IBAction func sendButtonTapped(_ sender: UIButton) {
let userInfo: [String: Any] = ["timestamp": Date()]
NotificationCenter.default.post(
name: .dataUpdated,
object: self,
userInfo: userInfo
)
}
}
// MARK: - Получатель
class ReceiverViewController: UIViewController {
private var token: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
token = NotificationCenter.default.addObserver(
forName: .dataUpdated,
object: nil,
queue: .main
) { [weak self] notification in
guard let self = self else { return }
if let timestamp = notification.userInfo?["timestamp"] as? Date {
print("Данные обновлены в \(timestamp)")
self.refreshUI()
}
}
}
deinit {
if let token = token {
NotificationCenter.default.removeObserver(token)
}
}
private func refreshUI() {
// Обновляем интерфейс
}
}
Ключевые моменты для запоминания
- Всегда используйте
#selectorдля методов, а не строки. - Помечайте метод-обработчик
@objc. - Определяйте имена уведомлений как
static letв расширенииNotification.Name. - Обязательно отписывайтесь в
deinit. - Для современного кода предпочитайте блоки с токеном — это безопаснее и читаемее.
Дополнительный подход: Использование Combine (iOS 13+)
Если ваше приложение поддерживает iOS 13 и выше, рекомендую рассмотреть фреймворк Combine. Он предоставляет более декларативный и безопасный способ работы с уведомлениями. Пример:
import Combine
class MyViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.publisher(for: .myCustomNotification)
.sink { [weak self] notification in
guard let self = self else { return }
if let message = notification.userInfo?["message"] as? String {
self.handleMessage(message)
}
}
.store(in: &cancellables)
}
private func handleMessage(_ message: String) {
print("Combine получил: \(message)")
}
}
Преимущества Combine:
- Автоматическая отписка при уничтожении
cancellables(не нужно писатьdeinit). - Чистый функциональный стиль.
- Лёгкая интеграция с другими операторами (map, filter, throttle и т.д.).
Однако помните: если ваше приложение должно работать на iOS 12 и ниже, Combine не подойдёт — используйте классический подход с токенами из первого ответа.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги