Observer — un pattern comportamental în care un obiect (editor) notifică mai mulți abonați despre modificările stării sale. În dezvoltarea mobilă, Observer stă la baza mecanicilor reactive: UI se abonează la modificările datelor și se actualizează automat. Pattern-ul este implementat în NotificationCenter pe iOS și LiveData/Flow pe Android. Mai multe — pe Refactoring Guru: Observer.
Principalele puncte
Observer (observator) — un pattern comportamental GoF care definește o dependență „unu la mulți" între obiecte. Când un obiect (Subject sau Observable) își schimbă starea, toate obiectele dependente (Observers) sunt notificate și actualizate automat. Pattern-ul implementează cuplarea slabă: editorul nu cunoaște clasele concrete ale abonaților — doar că acestea implementează interfața Observer.
Structura Observer include interfața Subject cu metodele attach(), detach(), notify() și interfața Observer cu metoda update(). ConcreteSubject stochează starea și lista de abonați. ConcreteObserver implementează update() și reacționează la modificări. În dezvoltarea mobilă, implementarea clasică GoF este rar întâlnită — este înlocuită de mecanisme încorporate: NotificationCenter, Combine, Flow, LiveData, care implementează aceeași idee cu API modern.
Modelul Push vs Pull — în modelul Push, Subject trimite date tuturor abonaților (NotificationCenter.post). În modelul Pull, Subject doar notifică, iar abonatul preia singur datele. Android LiveData folosește Push (datele sunt transmise în observe), RxJava/Flow suportă ambele modele. Alegerea depinde de sarcină: Push este mai simplu pentru actualizări UI, Pull este mai eficient pentru volume mari de date pe care abonatul poate nu dorește să le primească.
NotificationCenter — mecanismul încorporat iOS/macOS pentru implementarea Observer. Editorul trimite Notification prin NotificationCenter.default.post(name:, object:, userInfo:). Abonatul se înregistrează prin addObserver(forName:, queue:, using:). NotificationCenter suportă notificări numite (Notification.Name) și poate transmite orice date în userInfo. UIKeyboardWillShowNotification, UIApplicationDidEnterBackgroundNotification — exemple de sistem.
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
}
// Editor
NotificationCenter.default.post(
name: .userDidLogin,
object: nil,
userInfo: ["userId": "123"]
)
// Abonat
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 }
// Abonatul reacționează la eveniment
self?.loadProfile(userId: userId)
}
observers.append(observer)
}
func stopObserving() {
observers.forEach { NotificationCenter.default.removeObserver($0) }
observers.removeAll()
}
}
Combine framework — o alternativă reactivă modernă la NotificationCenter, disponibilă din iOS 13. Publisher (NotificationCenter, URLSession.Timer) — editor, Subscriber (sink, assign) — abonat. Combine adaugă operatori (map, filter, combineLatest) pentru transformarea fluxului de date. @Published — property wrapper care notifică automat abonații despre modificări. În MVVM cu SwiftUI, Combine înlocuiește NotificationCenter pentru legarea ViewModel și View.
KVO (Key-Value Observing) — un mecanism vechi ObjC/Swift pentru observarea proprietăților individuale ale obiectelor. @objc dynamic var name: String — proprietatea observată. observe(.name) — abonare. KVO funcționează doar cu clase compatibile @objc și moștenirea ObjC. Apple recomandă Combine și @Published în loc de KVO în proiecte noi. KVO rămâne relevant pentru compatibilitatea UIKit în proiecte hibride.
LiveData — o componentă Android Architecture Components pentru implementarea Observer. Clasă Observable care notifică abonații despre modificările datelor. LiveData ține cont de ciclul de viață: abonații (LifecycleOwner) se dezabonează automat la distrugere. LiveData este modelul Push: datele sunt transmise în observe(). LiveData este blocul de construcție de bază MVVM în Android înainte de introducerea Jetpack Compose.
// ViewModel — editor
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 — abonat
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.user.observe(viewLifecycleOwner) { user ->
// Abonatul reacționează la modificări
userName.text = user?.name
userEmail.text = user?.email
}
}
}
StateFlow și SharedFlow — tipuri reactive din Kotlin Coroutines care au înlocuit LiveData în Jetpack Compose. StateFlow — state holder observabil cu o valoare curentă fixă. SharedFlow — hot flow configurabil fără stare, potrivit pentru evenimente unice (navigare, toasturi). Ambele tipuri sunt strâns integrate cu Compose: collectAsState(), collectAsEffect(). StateFlow este obligatoriu în proiectele Android moderne cu Compose.
LiveData vs StateFlow — LiveData este legat de Android Lifecycle, StateFlow este independent de platformă. StateFlow suportă coroutines, operatori (map, filter) și se testează fără dependințe Android. LiveData este mai simplu pentru compatibilitatea cu Java. Google recomandă StateFlow pentru proiecte noi cu Kotlin + Compose, LiveData — pentru suportul proiectelor vechi sau cod Java.
Scurgeri de memorie (memory leaks) — principala problemă a Observer fără gestionarea corectă a abonamentelor. Dacă abonatul (Activity, Fragment, UIViewController) este distrus dar nu s-a dezabonat, editorul continuă să păstreze referința către el, iar garbage collector nu poate elibera memoria. În Android, LifecycleOwner (Activity/Fragment) trebuie să apeleze removeObserver() sau să folosească observe(viewLifecycleOwner). În iOS — removeObserver în deinit sau disposeBag în Combine.
| Platformă | Mecanism Observer | Dezabonare automată | Dezabonare manuală |
|---|---|---|---|
| iOS | NotificationCenter | Nu | removeObserver() în deinit |
| iOS | Combine (sink) | Nu | store(in: &bag) — DisposeBag |
| iOS | KVO | Nu | removeObserver() în deinit |
| Android | LiveData | Da (LifecycleOwner) | removeObserver() opțional |
| Android | StateFlow | Prin viewModelScope | cancel() Job la dezabonare |
| Android | RxJava | Nu | dispose() în CompositeDisposable |
Referință slabă (weak reference) în abonați — în blocul de abonare folosește [weak self] în Swift și referă-te la lifecycle scope în Kotlin. LiveData gestionează automat abonamentul prin LifecycleOwner — abonamentul este activ doar când Lifecycle este în starea STARTED sau RESUMED. StateFlow în Compose folosește collectAsState() ținând cont de lifecycle. NotificationCenter în iOS necesită [weak self] explicit, deoarece closure-ul face referință puternică la self.
Observer (GoF) și Publisher-Subscriber (PubSub) — pattern-uri similare, dar diferite. În Observer, editorul notifică direct abonații apelând metodele lor. Editorul știe despre abonați (stochează o listă). În PubSub, editorul și abonatul nu știu unul despre celălalt — între ei se află un intermediar (Event Bus, Message Queue, NotificationCenter). Editorul trimite un mesaj pe un canal, abonatul ascultă canalul. PubSub asigură o cuplare mai slabă.
Exemple de PubSub în dezvoltarea mobilă — NotificationCenter în iOS poate fi considerat PubSub: editorul nu cunoaște abonații — doar publică notificarea. EventBus sau Otto în Android (învechite). SharedFlow cu BroadcastChannel — PubSub în lumea Kotlin. În sisteme distribuite, PubSub se implementează prin RabbitMQ, Kafka, Google PubSub. Pentru dezvoltarea mobilă, PubSub este util în arhitectura modulară, unde modulele nu trebuie să depindă unele de altele.
Ce să alegi — pentru actualizări UI (ViewModel → View) folosește Observer (LiveData, StateFlow, @Published). Pentru evenimente intermodule (autentificare, deconectare, schimbare temă) — PubSub (SharedFlow, NotificationCenter, EventBus). Observer este mai simplu și mai eficient în cadrul unui ecran, PubSub este mai flexibil pentru evenimente globale, dar mai dificil de depanat din cauza dependințelor implicite.
Întrebări frecvente
StateFlow este un tip independent de platformă din Kotlin Coroutines, LiveData este legat de Android Lifecycle. StateFlow suportă coroutines și operatori, se testează fără Android. LiveData gestionează automat abonamentul prin LifecycleOwner. Google recomandă StateFlow pentru proiecte noi cu Kotlin + Compose, LiveData — pentru compatibilitatea cu Java.
Folosește [weak self] în closure-ul handler-ului și apelează removeObserver() în deinit. Păstrează referința la observer (NSObjectProtocol) și șterge-l la distrugerea obiectului. În Combine, folosește AnyCancellable și store(in:) pentru dezabonare automată la eliberarea DisposeBag.
Da, SwiftUI suportă ObservableObject cu @Published și @StateObject/@ObservedObject — aceasta este o implementare încorporată a Observer. @Published notifică automat View despre modificări. Combine nu este obligatoriu: ObservableObject folosește Publisher-ul objectWillChange încorporat în SwiftUI. Combine adaugă operatori pentru transformarea fluxurilor.
SharedFlow — pentru evenimente unice (navigare, toasturi, Snackbar) unde nu este necesară valoarea curentă. StateFlow — pentru starea UI (lista de date, progresul încărcării) unde este necesar un snapshot curent. SharedFlow nu are proprietatea value și nu returnează ultima valoare noilor abonați.
KVO este un mecanism vechi ObjC, necesită @objc dynamic și funcționează doar cu clase care moștenesc NSObject. Combine este un framework Swift modern, tip-securizat, cu operatori și integrare cu SwiftUI. Combine înlocuiește KVO și NotificationCenter. Apple recomandă Combine pentru proiecte noi, KVO — doar pentru suportul codului vechi.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și