В каких методах жизненного цикла UIViewController лучше подписываться и отписываться от NotificationCenter?

Ответ

Стратегия зависит от необходимости уведомлений:

1. Подписка на постоянные уведомления (нужны, пока контроллер жив)

  • Подписка: viewDidLoad()
  • Отписка: deinit
// viewDidLoad
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleDataUpdate),
        name: .dataDidUpdateNotification,
        object: nil
    )
}

// deinit
deinit {
    // Обязательная отписка для предотвращения утечек памяти
    NotificationCenter.default.removeObserver(self)
}

2. Подписка на временные уведомления (нужны только когда экран видим)

  • Подписка: viewWillAppear(_:)
  • Отписка: viewWillDisappear(_:)
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleAppBecameActive),
        name: UIApplication.didBecomeActiveNotification,
        object: nil
    )
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}

Best Practices:

  • Всегда отписывайтесь. Неотписанный наблюдатель удерживает контроллер в памяти.
  • В iOS 9+ и с использованием блоков отписываться явно не нужно, если использовать возвращаемый токен и хранить его в свойстве (он автоматически отпишется при деинициализации).
  • Для отписки от конкретного уведомления используйте removeObserver(_:name:object:) — это точнее, чем removeObserver(_:).

Ответ 18+ 🔞

Эй, а вот это, блядь, интересный вопрос! Сидишь ты такой, пишешь приложение, и тут бац — надо ловить эти ваши нотификации. А где их ловить-то, сука? И главное — где отпускать, чтобы память не текла, как решето? Сейчас разжуём, блядь.

Вот смотри, стратегия-то зависит от того, насколько долго тебе эти уведомления нужны. Как будто ты в кино: если фильм на весь сеанс — сиди с начала до конца. А если вышел в туалет — зачем тебе звук из зала? Вернулся — опять подписался. Вот и тут так же.

1. Подписка на постоянные уведомления (нужны, пока контроллер жив, как таракан в щели)

  • Где вешаем уши: viewDidLoad() — как только контроллер загрузился, сразу слушаем.
  • Где снимаем уши: deinit — когда контроллер накрывается медным тазом, надо всё отцепить, а то память потечёт, пиздец.
// viewDidLoad
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleDataUpdate),
        name: .dataDidUpdateNotification,
        object: nil
    )
}

// deinit
deinit {
    // Это, блядь, ОБЯЗАТЕЛЬНО! Иначе твой контроллер будет висеть в памяти, как призрак, и сосать ресурсы. Отписываемся нахуй.
    NotificationCenter.default.removeObserver(self)
}

2. Подписка на временные уведомления (нужны только когда экран торчит перед глазами)

  • Где вешаем уши: viewWillAppear(_:) — вот-вот появимся на экране, пора слушать.
  • Где снимаем уши: viewWillDisappear(_:) — только собрались свалить — сразу отписались, чтобы не слушать за спиной, как идиоты.
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleAppBecameActive),
        name: UIApplication.didBecomeActiveNotification,
        object: nil
    )
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // Отписываемся только от конкретного уведомления, а не ото всех, как слоупоки.
    NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}

Лайфхаки, блядь, чтобы не обосраться:

  • Отписывайся всегда, ёпта. Забыл отписаться — получил утечку памяти. Твой контроллер будет жить вечно в аду, и сборщик мусора его не возьмёт.
  • Если ты не лох и пишешь под iOS 9+, да ещё и с блоками, то можно по-хитрому: сохранить токен, который addObserver возвращает, в свойство. Когда контроллер сдохнет — токен умрёт вместе с ним и всё само отпишется. Красота, блядь!
  • Не дрочи removeObserver(_:) на всё подряд. Лучше тыкать в конкретное уведомление через removeObserver(_:name:object:) — так точнее, и меньше шансов, что ты случайно отпишешься от чего-то ещё, что тебе нужно.