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, оператори (map, filter) та тестується без Android-залежностей. LiveData простіший для Java-сумісності. Google рекомендує StateFlow для нових проєктів на Kotlin + Compose, LiveData — для підтримки старих проєктів або Java-коду.
Витоки пам'яті — головна проблема 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.