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 — наблюдаем state holder с фиксирана текуща стойност. SharedFlow — конфигурируем hot flow без състояние, подходящ за еднократни събития (навигация, toast). И двата типа са тясно интегрирани с 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 код.
Изтичане на памет (memory leaks) — основният проблем на Observer без правилно управление на абонаменти. Ако абонат (Activity, Fragment, UIViewController) е унищожен, но не се е отписал, издателят продължава да държи референция към него и garbage collector не може да освободи паметта. В 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], тъй като closure силно референцира 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] в closure на handler и извикайте removeObserver() в deinit. Съхранявайте референция към observer (NSObjectProtocol) и я премахнете при унищожаване на обекта. В Combine използвайте AnyCancellable и store(in:) за автоматично отписване при освобождаване на DisposeBag.
Да, SwiftUI поддържа ObservableObject с @Published и @StateObject/@ObservedObject — това е вградена реализация на Observer. @Published автоматично уведомява View за промени. Combine не е задължителен: ObservableObject използва вградения в SwiftUI objectWillChange Publisher. Combine добавя оператори за трансформация на потоци.
SharedFlow — за еднократни събития (навигация, toast, Snackbar), където не е необходима текуща стойност. StateFlow — за UI състояние (списък с данни, прогрес на зареждане), където е необходима текуща моментна снимка. SharedFlow няма свойство value и не връща последната стойност на нови абонати.
KVO е остарял ObjC механизъм, изисква @objc dynamic и работи само с класове, наследяващи NSObject. Combine е модерен Swift framework, типобезопасен, с оператори и интеграция със SwiftUI. Combine замества KVO и NotificationCenter. Apple препоръчва Combine за нови проекти, KVO — само за поддръжка на стар код.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също