Observer — wzorzec behawioralny, w którym jeden obiekt (wydawca) powiadamia wielu subskrybentów o zmianach swojego stanu. W programowaniu mobilnym Observer leży u podstaw mechanik reaktywnych: UI subskrybuje zmiany danych i automatycznie się aktualizuje. Wzorzec jest zaimplementowany w NotificationCenter na iOS i LiveData/Flow na Android. Więcej — na Refactoring Guru: Observer.
Najważniejsze
Observer (obserwator) — wzorzec behawioralny GoF definiujący zależność „jeden do wielu" między obiektami. Gdy jeden obiekt (Subject lub Observable) zmienia stan, wszystkie zależne obiekty (Observers) są automatycznie powiadamiane i aktualizowane. Wzorzec realizuje słabe powiązanie: wydawca nie zna konkretnych klas subskrybentów — tylko to, że implementują interfejs Observer.
Struktura Observer obejmuje interfejs Subject z metodami attach(), detach(), notify() oraz interfejs Observer z metodą update(). ConcreteSubject przechowuje stan i listę subskrybentów. ConcreteObserver implementuje update() i reaguje na zmiany. W programowaniu mobilnym klasyczna implementacja GoF występuje rzadko — zastępują ją wbudowane mechanizmy: NotificationCenter, Combine, Flow, LiveData, które realizują tę samą ideę z nowoczesnym API.
Model Push vs Pull — w modelu Push Subject wysyła dane do wszystkich subskrybentów (NotificationCenter.post). W modelu Pull Subject tylko powiadamia, a subskrybent sam pobiera dane. Android LiveData używa Push (dane są przekazywane w observe), RxJava/Flow obsługują oba modele. Wybór zależy od zadania: Push jest prostszy dla aktualizacji UI, Pull wydajniejszy dla dużych wolumenów danych, które subskrybent może nie chcieć otrzymywać.
NotificationCenter — wbudowany mechanizm iOS/macOS do implementacji Observer. Wydawca wysyła Notification przez NotificationCenter.default.post(name:, object:, userInfo:). Subskrybent rejestruje się przez addObserver(forName:, queue:, using:). NotificationCenter obsługuje nazwane powiadomienia (Notification.Name) i może przekazywać dowolne dane w userInfo. UIKeyboardWillShowNotification, UIApplicationDidEnterBackgroundNotification — systemowe przykłady.
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
}
// Wydawca
NotificationCenter.default.post(
name: .userDidLogin,
object: nil,
userInfo: ["userId": "123"]
)
// Subskrybent
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 }
// Subskrybent reaguje na zdarzenie
self?.loadProfile(userId: userId)
}
observers.append(observer)
}
func stopObserving() {
observers.forEach { NotificationCenter.default.removeObserver($0) }
observers.removeAll()
}
}
Combine framework — nowoczesna reaktywna alternatywa dla NotificationCenter, dostępna od iOS 13. Publisher (NotificationCenter, URLSession.Timer) — wydawca, Subscriber (sink, assign) — subskrybent. Combine dodaje operatory (map, filter, combineLatest) do transformacji strumienia danych. @Published — property wrapper automatycznie powiadamiający subskrybentów o zmianach. W MVVM z SwiftUI Combine zastępuje NotificationCenter do łączenia ViewModel i View.
KVO (Key-Value Observing) — stary mechanizm ObjC/Swift do obserwacji pojedynczych właściwości obiektów. @objc dynamic var name: String — obserwowana właściwość. observe(.name) — subskrypcja. KVO działa tylko z klasami zgodnymi z @objc i dziedzictwem ObjC. Apple zaleca Combine i @Published zamiast KVO w nowych projektach. KVO pozostaje aktualny dla zgodności z UIKit w projektach hybrydowych.
LiveData — komponent Android Architecture Components do implementacji Observer. Klasa Observable, która powiadamia subskrybentów o zmianach danych. LiveData uwzględnia cykl życia: subskrybenci (LifecycleOwner) automatycznie wypisują się przy zniszczeniu. LiveData to model Push: dane są przekazywane w observe(). LiveData to podstawowy blok budulcowy MVVM w Android przed wprowadzeniem Jetpack Compose.
// ViewModel — wydawca
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 — subskrybent
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.user.observe(viewLifecycleOwner) { user ->
// Subskrybent reaguje na zmiany
userName.text = user?.name
userEmail.text = user?.email
}
}
}
StateFlow i SharedFlow — reaktywne typy z Kotlin Coroutines, które zastąpiły LiveData w Jetpack Compose. StateFlow — observable state holder z ustaloną bieżącą wartością. SharedFlow — konfigurowalny hot flow bez stanu, odpowiedni do jednorazowych zdarzeń (nawigacja, toasty). Oba typy są ściśle zintegrowane z Compose: collectAsState(), collectAsEffect(). StateFlow jest obowiązkowy w nowoczesnych projektach Android z Compose.
LiveData vs StateFlow — LiveData jest związany z Android Lifecycle, StateFlow jest platformoniezależny. StateFlow obsługuje coroutines, operatory (map, filter) i jest testowalny bez zależności Android. LiveData jest prostszy dla zgodności z Java. Google zaleca StateFlow dla nowych projektów na Kotlin + Compose, LiveData — dla wsparcia starych projektów lub kodu Java.
Wycieki pamięci (memory leaks) — główny problem Observer bez prawidłowego zarządzania subskrypcjami. Jeśli subskrybent (Activity, Fragment, UIViewController) został zniszczony, ale nie wypisał się, wydawca nadal trzyma do niego referencję, a garbage collector nie może zwolnić pamięci. W Android LifecycleOwner (Activity/Fragment) powinien wywołać removeObserver() lub użyć observe(viewLifecycleOwner). W iOS — removeObserver w deinit lub disposeBag w Combine.
| Platforma | Mechanizm Observer | Automatyczne wypisywanie | Ręczne wypisywanie |
|---|---|---|---|
| iOS | NotificationCenter | Nie | removeObserver() w deinit |
| iOS | Combine (sink) | Nie | store(in: &bag) — DisposeBag |
| iOS | KVO | Nie | removeObserver() w deinit |
| Android | LiveData | Tak (LifecycleOwner) | removeObserver() opcjonalnie |
| Android | StateFlow | Przez viewModelScope | cancel() Job przy wypisywaniu |
| Android | RxJava | Nie | dispose() w CompositeDisposable |
Słaba referencja (weak reference) w subskrybentach — w bloku subskrypcji używaj [weak self] w Swift i odwołuj się do lifecycle scope w Kotlin. LiveData automatycznie zarządza subskrypcją przez LifecycleOwner — subskrypcja jest aktywna tylko gdy Lifecycle jest w stanie STARTED lub RESUMED. StateFlow w Compose używa collectAsState() z uwzględnieniem lifecycle. NotificationCenter w iOS wymaga jawnego [weak self], ponieważ domknięcie silnie referencjonuje self.
Observer (GoF) i Publisher-Subscriber (PubSub) — podobne, ale różne wzorce. W Observer wydawca bezpośrednio powiadamia subskrybentów przez wywołanie ich metod. Wydawca wie o subskrybentach (przechowuje listę). W PubSub wydawca i subskrybent nie wiedzą o sobie nawzajem — między nimi znajduje się pośrednik (Event Bus, Message Queue, NotificationCenter). Wydawca wysyła wiadomość na kanał, subskrybent słucha kanału. PubSub zapewnia słabsze powiązanie.
Przykłady PubSub w programowaniu mobilnym — NotificationCenter w iOS można uznać za PubSub: wydawca nie zna subskrybentów — po prostu publikuje powiadomienie. EventBus lub Otto w Android (przestarzałe). SharedFlow z BroadcastChannel — PubSub w świecie Kotlin. W systemach rozproszonych PubSub jest realizowany przez RabbitMQ, Kafka, Google PubSub. W programowaniu mobilnym PubSub jest przydatny w architekturze modułowej, gdzie moduły nie powinny zależeć od siebie.
Co wybrać — do aktualizacji UI (ViewModel → View) używaj Observer (LiveData, StateFlow, @Published). Do zdarzeń międzymodułowych (autoryzacja, wylogowanie, zmiana motywu) — PubSub (SharedFlow, NotificationCenter, EventBus). Observer jest prostszy i wydajniejszy w obrębie jednego ekranu, PubSub jest bardziej elastyczny dla zdarzeń globalnych, ale trudniejszy w debugowaniu z powodu niejawnych zależności.
Często zadawane pytania
StateFlow to platformoniezależny typ z Kotlin Coroutines, LiveData jest związany z Android Lifecycle. StateFlow obsługuje coroutines i operatory, testuje się bez Android. LiveData automatycznie zarządza subskrypcją przez LifecycleOwner. Google zaleca StateFlow dla nowych projektów na Kotlin + Compose, LiveData — dla zgodności z Java.
Używaj [weak self] w domknięciu handlera i wywołuj removeObserver() w deinit. Przechowuj referencję do observer (NSObjectProtocol) i usuwaj go przy zniszczeniu obiektu. W Combine używaj AnyCancellable i store(in:) do automatycznego wypisywania przy zwolnieniu DisposeBag.
Tak, SwiftUI obsługuje ObservableObject z @Published i @StateObject/@ObservedObject — to wbudowana implementacja Observer. @Published automatycznie powiadamia View o zmianach. Combine nie jest wymagany: ObservableObject używa wbudowanego w SwiftUI wydawcy objectWillChange Publisher. Combine dodaje operatory do transformacji strumieni.
SharedFlow — do jednorazowych zdarzeń (nawigacja, toasty, Snackbar), gdzie nie jest potrzebna bieżąca wartość. StateFlow — do stanu UI (lista danych, postęp ładowania), gdzie potrzebny jest bieżący snapshot. SharedFlow nie ma właściwości value i nie zwraca ostatniej wartości nowym subskrybentom.
KVO to przestarzały mechanizm ObjC, wymaga @objc dynamic i działa tylko z klasami dziedziczącymi po NSObject. Combine to nowoczesny framework Swift, typowo bezpieczny, z operatorami i integracją ze SwiftUI. Combine zastępuje KVO i NotificationCenter. Apple zaleca Combine dla nowych projektów, KVO — tylko do wsparcia starszego kodu.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również