Observer — поведенческий паттерн, в котором один объект (издатель) уведомляет множество подписчиков об изменениях своего состояния. В мобильной разработке Observer лежит в основе реактивных механик: UI подписывается на изменения данных и автоматически обновляется. Паттерн реализован в NotificationCenter на iOS и LiveData/Flow на Android. Подробнее — в Refactoring Guru: Observer.
Главное
Observer (наблюдатель) — поведенческий паттерн GoF, определяющий зависимость «один ко многим» между объектами. Когда один объект (Subject или Observable) меняет состояние, все зависимые объекты (Observers) автоматически уведомляются и обновляются. Паттерн реализует слабую связанность: издатель не знает конкретных классов подписчиков — только то, что они реализуют интерфейс Observer.
Структура Observer включает интерфейс Subject с методами attach(), detach(), notify() и интерфейс Observer с методом update(). ConcreteSubject хранит состояние и список подписчиков. ConcreteObserver реализует update() и реагирует на изменения. В мобильной разработке классическая реализация GoF встречается редко — её заменяют встроенные механизмы: NotificationCenter, Combine, Flow, LiveData, которые реализуют ту же идею с современным API.
Push vs Pull модель — в Push-модели Subject отправляет данные всем подписчикам (NotificationCenter.post). В Pull-модели Subject только уведомляет, а подписчик сам запрашивает данные. Android LiveData использует Push (данные передаются в observe), RxJava/Flow поддерживают обе модели. Выбор зависит от задачи: Push проще для UI-обновлений, Pull эффективнее для больших объёмов данных, которые подписчик может не захотеть получать.
NotificationCenter — встроенный механизм iOS/macOS для реализации Observer. Издатель отправляет Notification через NotificationCenter.default.post(name:, object:, userInfo:). Подписчик регистрируется через addObserver(forName:, queue:, using:). NotificationCenter поддерживает именованные уведомления (Notification.Name) и может передавать любые данные в userInfo. UIKeyboardWillShowNotification, UIApplicationDidEnterBackgroundNotification — системные примеры.
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
}
// Издатель
NotificationCenter.default.post(
name: .userDidLogin,
object: nil,
userInfo: ["userId": "123"]
)
// Подписчик
class ProfileViewModel {
private var observers: [NSObjectProtocol] = []
func startObserving() {
let observer = NotificationCenter.default.addObserver(
forName: .userDidLogin,
object: nil,
queue: .main
) { [weak self] notification in
guard let userId = notification.userInfo?["userId"] as? String else { return }
// Подписчик реагирует на событие
self?.loadProfile(userId: userId)
}
observers.append(observer)
}
func stopObserving() {
observers.forEach { NotificationCenter.default.removeObserver($0) }
observers.removeAll()
}
}
Combine framework — современная реактивная альтернатива NotificationCenter, появившаяся в iOS 13. Publisher (NotificationCenter, URLSession.Timer) — издатель, Subscriber (sink, assign) — подписчик. Combine добавляет операторы (map, filter, combineLatest) для трансформации потока данных. @Published — property wrapper, автоматически уведомляющий подписчиков об изменениях. В MVVM со SwiftUI Combine заменяет NotificationCenter для связывания ViewModel и View.
KVO (Key-Value Observing) — старый механизм ObjC/Swift для наблюдения за отдельными свойствами объектов. @objc dynamic var name: String — наблюдаемое свойство. observe(.name) — подписка. KVO работает только с @objc-совместимыми классами и наследием ObjC. Apple рекомендует Combine и @Published вместо KVO в новых проектах. KVO остаётся актуальным для UIKit-совместимости в гибридных проектах.
LiveData — компонент Android Architecture Components для реализации Observer. Observable-класс, который уведомляет подписчиков об изменениях данных. LiveData учитывает жизненный цикл: подписчики (LifecycleOwner) автоматически отписываются при уничтожении. LiveData — Push-модель: данные передаются в observe(). LiveData — базовый строительный блок MVVM в Android до внедрения Jetpack Compose.
// ViewModel — издатель
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User?>(null)
val user: LiveData<User?> = _user
fun loadUser(id: String) {
viewModelScope.launch {
val result = userRepository.getUser(id)
_user.value = result
}
}
}
// Fragment — подписчик
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.user.observe(viewLifecycleOwner) { user ->
// Подписчик реагирует на изменения
userName.text = user?.name
userEmail.text = user?.email
}
}
}
StateFlow и SharedFlow — реактивные типы из Kotlin Coroutines, заменившие LiveData в Jetpack Compose. StateFlow — observable state holder с фиксированным текущим значением. SharedFlow — конфигурируемый hot flow без состояния, подходящий для одноразовых событий (навигация, тосты). Оба типа тесно интегрированы с Compose: collectAsState(), collectAsEffect(). StateFlow обязателен в современных Android-проектах с Compose.
LiveData vs StateFlow — LiveData привязан к Android Lifecycle, StateFlow — платформонезависимый. StateFlow поддерживает coroutines, operators (map, filter) и тестируется без Android-зависимостей. LiveData проще для Java-совместимости. Google рекомендует StateFlow для новых проектов на Kotlin + Compose, LiveData — для поддержки старых проектов или Java-кода.
Утечки памяти (memory leaks) — главная проблема Observer без правильного управления подписками. Если подписчик (Activity, Fragment, UIViewController) уничтожен, но не отписан, издатель продолжает держать ссылку на него, и сборщик мусора не может освободить память. В Android LifecycleOwner (Activity/Fragment) должен вызвать removeObserver() или использовать observe(viewLifecycleOwner). В iOS — removeObserver в deinit или disposeBag в Combine.
| Платформа | Механизм Observer | Автоматическая отписка | Ручная отписка |
|---|---|---|---|
| iOS | NotificationCenter | Нет | removeObserver() в deinit |
| iOS | Combine (sink) | Нет | store(in: &bag) — DisposeBag |
| iOS | KVO | Нет | removeObserver() в deinit |
| Android | LiveData | Да (LifecycleOwner) | removeObserver() опционально |
| Android | StateFlow | Через viewModelScope | cancel() Job при отписке |
| Android | RxJava | Нет | dispose() в CompositeDisposable |
Weak reference в подписчиках — при замыкании в блоке подписки используйте [weak self] в Swift и ссылайтесь на lifecycle scope в Kotlin. LiveData автоматически управляет подпиской через LifecycleOwner — подписка активна только когда Lifecycle в состоянии STARTED или RESUMED. StateFlow в Compose использует collectAsState() с учётом lifecycle. NotificationCenter в iOS требует явного [weak self], так как замыкание сильно ссылается на self.
Observer (GoF) и Publisher-Subscriber (PubSub) — похожие, но разные паттерны. В Observer издатель напрямую уведомляет подписчиков через вызов их методов. Издатель знает о подписчиках (хранит список). В PubSub издатель и подписчик не знают друг о друге — между ними находится посредник (Event Bus, Message Queue, NotificationCenter). Издатель отправляет сообщение на канал, подписчик слушает канал. PubSub — более слабая связанность.
Примеры PubSub в мобильной разработке — NotificationCenter в iOS можно считать PubSub: издатель не знает подписчиков — он просто постит уведомление. EventBus или Otto в Android (устаревшие). SharedFlow с BroadcastChannel — PubSub в мире Kotlin. В распределённых системах PubSub реализуется через RabbitMQ, Kafka, Google PubSub. Для мобильной разработки PubSub полезен при модульной архитектуре, где модули не должны зависеть друг от друга.
Что выбрать — для UI-обновлений (ViewModel → View) используйте Observer (LiveData, StateFlow, @Published). Для межмодульных событий (авторизация, выход из аккаунта, смена темы) — PubSub (SharedFlow, NotificationCenter, EventBus). Observer проще и эффективнее внутри одного экрана, PubSub гибче для глобальных событий, но сложнее в отладке из-за неявных зависимостей.
Часто задаваемые вопросы
StateFlow — платформонезависимый тип из Kotlin Coroutines, LiveData привязан к Android Lifecycle. StateFlow поддерживает coroutines и операторы, тестируется без Android. LiveData автоматически управляет подпиской через LifecycleOwner. Google рекомендует StateFlow для новых проектов на Kotlin + Compose, LiveData — для Java-совместимости.
Используйте [weak self] в замыкании-обработчике и вызывайте removeObserver() в deinit. Храните ссылку на observer (NSObjectProtocol) и удаляйте его при уничтожении объекта. В Combine используйте AnyCancellable и store(in:) для автоматической отписки при освобождении DisposeBag.
Да, SwiftUI поддерживает ObservableObject с @Published и @StateObject/@ObservedObject — это встроенная реализация Observer. @Published автоматически уведомляет View об изменениях. Combine не обязателен: ObservableObject использует objectWillChange Publisher, встроенный в SwiftUI. Combine добавляет операторы для трансформации потоков.
SharedFlow — для одноразовых событий (навигация, тосты, Snackbar), где не нужно текущее значение. StateFlow — для состояния UI (список данных, прогресс загрузки), где нужен текущий снимок. SharedFlow не имеет value-свойства и не возвращает последнее значение новым подписчикам.
KVO — устаревший механизм ObjC, требует @objc dynamic и работает только с классами, унаследованными от NSObject. Combine — современный Swift-фреймворк, типобезопасный, с операторами и интеграцией со SwiftUI. Combine заменяет KVO и NotificationCenter. Apple рекомендует Combine для новых проектов, KVO — только для поддержки легаси.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также