Как используются наблюдатели (обзерверы) в iOS
Что проверяют
Знаете ли вы механизмы наблюдения в iOS и чем они различаются по связности, типобезопасности и управлению памятью. Следом обычно спрашивают, когда нужно отписываться и как не получить цикл сильных ссылок.
Как отвечать
Назовите паттерн одной фразой: объект подписывается на изменения другого и не знает, кто ещё подписан. Дальше конкретные механизмы:
NotificationCenter: широковещательные события «один ко многим», отправитель не знает получателей. Уведомление доставляется синхронно в том потоке, где его отправили, поэтому UI в обработчике обновляют на главном потоке;- KVO: наблюдение за свойством конкретного объекта. В Swift это
observe(\.property, options: [.new]), метод возвращаетNSKeyValueObservation, и наблюдение живёт, пока жив токен. Работает только для наследниковNSObjectсо свойствами@objc dynamic; - Combine:
@PublishedиNotificationCenter.default.publisher(for:), подписка хранится вSet<AnyCancellable>и отменяется вместе с ним; - Observation: макрос
@Observableс iOS 17. SwiftUI сам отслеживает, какие свойства прочитаны вbody, и перерисовывает только зависящие от них представления; didSetиwillSet: наблюдатели свойства внутри своего же типа, самый простой вариант.
Сравните с делегатом: это связь «один к одному» с возможностью вернуть значение, а наблюдатель это рассылка без ответа.
По памяти: с iOS 9 наблюдателя на селекторе в NotificationCenter удалять вручную не обязательно. Вариант с замыканием addObserver(forName:object:queue:using:) возвращает токен, его нужно хранить и снимать самому, а внутри замыкания брать [weak self].
Где спотыкаются
Используют NotificationCenter как общую шину для всего подряд. Через год никто не скажет, кто и когда шлёт событие, а данные ходят через нетипизированный userInfo.
Захватывают self сильно в замыкании подписки. Контроллер не освобождается, и после закрытия экрана обработчик продолжает срабатывать.
Вешают KVO на обычное свойство Swift-класса без @objc dynamic и удивляются, что наблюдение молчит. Или хранят NSKeyValueObservation в локальной переменной, и подписка умирает сразу после выхода из метода.