Как правильно использовать NSNotificationCenter addObserver в Swift?
Проблема и её решение
Вы столкнулись с типичной ситуацией: используете старый API из Objective-C, который в Swift считается устаревшим. Начиная с iOS 9 и Swift 3, Apple рекомендует использовать addObserver(forName:object:queue:using:) или, что ещё лучше, современные подходы на основе замыканий (closures) и NotificationCenter.
Правильный способ в современном Swift
Вот как должен выглядеть ваш код:
import UIKit
class ViewController: UIViewController {
private var notificationObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
// Подписка через замыкание (рекомендуемый способ)
notificationObserver = NotificationCenter.default.addObserver(
forName: NSNotification.Name("LanguageChanged"),
object: nil,
queue: .main
) { [weak self] notification in
self?.languageChanged(notification)
}
}
private func languageChanged(_ notification: Notification) {
print("Language changed!")
// Здесь обновляем UI
}
deinit {
// Обязательно удаляем наблюдателя
if let observer = notificationObserver {
NotificationCenter.default.removeObserver(observer)
}
}
}
Ключевые моменты
- Замыкание вместо селектора: Используйте
addObserver(forName:object:queue:using:)— он принимает замыкание, а не селектор. Это типобезопасно и избавляет от строковых констант. - weak self: Обязательно используйте
[weak self]в замыкании, чтобы избежать цикла сильных ссылок (retain cycle). - Хранение ссылки на наблюдателя: Метод возвращает объект типа
NSObjectProtocol, который нужно сохранить (например, в свойстве класса). - Удаление в deinit: Вы обязаны удалить наблюдателя в
deinit. Иначе, если объект будет уничтожен, но наблюдатель останется, при следующей отправке уведомления произойдёт обращение к освобождённой памяти (crash).
Альтернатива: блоки и селекторы
Если вам нужна обратная совместимость со старым кодом, можно использовать селектор, но с модификацией:
NotificationCenter.default.addObserver(
self,
selector: #selector(languageChanged(_:)),
name: NSNotification.Name("LanguageChanged"),
object: nil
)
@objc func languageChanged(_ notification: Notification) {
print("Language changed!")
}
Здесь важно:
- Добавить
@objcперед функцией-обработчиком. - Использовать
#selectorвместо строки. - Всё равно удалять наблюдателя в
deinit.
Отправка уведомления
Отправка тоже изменилась:
// Старый способ (ещё работает, но не рекомендуется)
NSNotificationCenter.defaultCenter().postNotificationName("LanguageChanged", object: nil)
// Современный способ
NotificationCenter.default.post(name: NSNotification.Name("LanguageChanged"), object: nil)
// Можно добавить userInfo для передачи данных
let userInfo: [String: Any] = ["language": "ru"]
NotificationCenter.default.post(
name: NSNotification.Name("LanguageChanged"),
object: nil,
userInfo: userInfo
)
Итоговые рекомендации
- Всегда используйте
NotificationCenter.default.addObserver(forName:object:queue:using:)— это современный, безопасный и читаемый подход. - Никогда не забывайте удалять наблюдателя в
deinit. Лучше всего использовать свойство для хранения ссылки на наблюдателя. - Для строковых имён уведомлений создавайте константы или используйте расширения, чтобы избежать опечаток.
- Рассмотрите альтернативы: делегаты, KVO, Combine (начиная с iOS 13) или реактивные фреймворки (RxSwift).
Использование Combine для уведомлений (iOS 13+)
Если ваше приложение поддерживает iOS 13 и выше, рекомендую использовать фреймворк Combine. Он предоставляет более декларативный и безопасный способ работы с уведомлениями.
import Combine
import UIKit
class ViewController: UIViewController {
private var cancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
cancellable = NotificationCenter.default.publisher(
for: NSNotification.Name("LanguageChanged")
)
.sink { [weak self] notification in
self?.languageChanged(notification)
}
}
private func languageChanged(_ notification: Notification) {
print("Language changed via Combine!")
}
deinit {
cancellable?.cancel()
}
}
Преимущества Combine
- Автоматическое управление памятью: При уничтожении объекта
AnyCancellableподписка автоматически отменяется. Не нужно явно вызыватьremoveObserver. - Цепочки операторов: Можно легко фильтровать, трансформировать и комбинировать уведомления.
- Потокобезопасность: Легко переключать получение уведомлений на главный поток с помощью
.receive(on: DispatchQueue.main).
Однако, если вам нужна поддержка старых версий iOS, используйте первый вариант с замыканиями — он универсален и работает начиная с iOS 9.
Ответов пока нет.
Войдите чтобы оставить ответ