Avatar ·

Как правильно использовать NSNotificationCenter (addObserver) в Swift: разбираем синтаксис и отписку

Привет! Я только начал изучать Swift и пытаюсь реализовать обмен данными между двумя ViewController через NSNotificationCenter. В Objective-C я видел такой синтаксис: ```objc [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"MyNotification" object:nil]; ``` Но в Swift я запутался. Пытаюсь написать так: ```swift NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleNotification:", name: "MyNotification", object: nil) ``` Но Xcode ругается на селектор — говорит, что нужно использовать #selector. Я переписал: ```swift NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(handleNotification), name: "MyNotification", object: nil) ``` Но теперь не понимаю, как правильно объявить метод handleNotification. Должен ли он принимать Notification? И ещё: я слышал, что нужно обязательно отписываться от уведомлений, иначе будет утечка памяти. Как это сделать правильно в 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 не подойдёт — используйте классический подход с токенами из первого ответа.