Observer — 하나의 객체(발행자)가 여러 구독자에게 상태 변경을 알리는 행동 패턴입니다. 모바일 개발에서 Observer는 반응형 메커니즘의 기초입니다. UI가 데이터 변경을 구독하고 자동으로 업데이트됩니다. 이 패턴은 iOS의 NotificationCenter와 Android의 LiveData/Flow에 구현되어 있습니다. 자세한 내용은 Refactoring Guru: Observer를 참조하세요.
핵심 포인트
Observer — 객체 간의 일대다 의존성을 정의하는 GoF 행동 패턴입니다. 하나의 객체(Subject 또는 Observable)가 상태를 변경하면 모든 종속 객체(Observers)가 자동으로 알림을 받고 업데이트됩니다. 이 패턴은 느슨한 결합을 구현합니다. 발행자는 구독자의 특정 클래스를 알지 못하며, Observer 인터페이스를 구현한다는 것만 알고 있습니다.
Observer 구조에는 attach(), detach(), notify() 메서드가 있는 Subject 인터페이스와 update() 메서드가 있는 Observer 인터페이스가 포함됩니다. 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 — Observer를 구현하기 위한 iOS/macOS의 내장 메커니즘입니다. 발행자는 NotificationCenter.default.post(name:, object:, userInfo:)를 통해 Notification을 보냅니다. 구독자는 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 프레임워크 — iOS 13에서 도입된 NotificationCenter의 현대적인 반응형 대안입니다. Publisher(NotificationCenter, URLSession, Timer) — 발행자, Subscriber(sink, assign) — 구독자. Combine은 데이터 스트림 변환을 위한 연산자(map, filter, combineLatest)를 추가합니다. @Published — 변경 사항을 구독자에게 자동으로 알리는 프로퍼티 래퍼입니다. SwiftUI를 사용한 MVVM에서 Combine은 ViewModel과 View를 바인딩하기 위해 NotificationCenter를 대체합니다.
KVO(Key-Value Observing) — 객체의 개별 속성을 관찰하기 위한 이전 ObjC/Swift 메커니즘입니다. @objc dynamic var name: String — 관찰 가능한 속성. observe(.name) — 구독. KVO는 @objc 호환 클래스 및 ObjC 상속에서만 작동합니다. Apple은 새 프로젝트에서 KVO 대신 Combine과 @Published를 권장합니다. KVO는 하이브리드 프로젝트에서 UIKit 호환성을 위해 여전히 중요합니다.
LiveData — Observer를 구현하기 위한 Android Architecture Components의 구성 요소입니다. 데이터 변경을 구독자에게 알리는 관찰 가능한 클래스입니다. LiveData는 수명 주기를 인식합니다. 구독자(LifecycleOwner)는 소멸될 때 자동으로 구독을 취소합니다. LiveData는 Push 모델을 사용합니다(데이터는 observe()로 전달됨). LiveData는 Jetpack Compose 이전 Android에서 MVVM의 기본 구성 요소입니다.
// 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 — Jetpack Compose에서 LiveData를 대체한 Kotlin Coroutines의 반응형 유형입니다. StateFlow — 고정된 현재 값을 가진 관찰 가능한 상태 홀더. SharedFlow — 상태가 없는 구성 가능한 핫 플로우로, 일회성 이벤트(탐색, 토스트)에 적합합니다. 두 유형 모두 Compose와 긴밀하게 통합됩니다: collectAsState(), collectAsEffect(). Compose를 사용하는 현대 Android 프로젝트에서는 StateFlow가 필수입니다.
LiveData vs StateFlow — LiveData는 Android 수명 주기에 바인딩되고, StateFlow는 플랫폼 독립적입니다. StateFlow는 코루틴, 연산자(map, filter)를 지원하며 Android 종속성 없이 테스트할 수 있습니다. LiveData는 Java 호환성에 더 간단합니다. Google은 Kotlin + Compose를 사용하는 새 프로젝트에는 StateFlow를, 레거시 프로젝트 또는 Java 코드 유지에는 LiveData를 권장합니다.
메모리 누수 — 적절한 구독 관리 없이 Observer를 사용할 때의 주요 문제입니다. 구독자(Activity, Fragment, UIViewController)가 소멸되었지만 구독이 취소되지 않은 경우, 발행자는 해당 참조를 계속 유지하고 가비지 컬렉터가 메모리를 해제할 수 없습니다. Android에서는 LifecycleOwner(Activity/Fragment)가 removeObserver()를 호출하거나 observe(viewLifecycleOwner)를 사용해야 합니다. iOS에서는 deinit에서 removeObserver를 호출하거나 Combine에서 disposeBag을 사용합니다.
| 플랫폼 | Observer 메커니즘 | 자동 구독 취소 | 수동 구독 취소 |
|---|---|---|---|
| iOS | NotificationCenter | 아니요 | deinit의 removeObserver() |
| iOS | Combine (sink) | 아니요 | store(in: &bag) — DisposeBag |
| iOS | KVO | 아니요 | deinit의 removeObserver() |
| Android | LiveData | 예 (LifecycleOwner) | removeObserver() 선택사항 |
| Android | StateFlow | viewModelScope를 통해 | 취소 시 cancel() Job |
| Android | RxJava | 아니요 | CompositeDisposable의 dispose() |
구독자의 약한 참조 — 구독 블록에서 클로저를 사용할 때 Swift에서는 [weak self]를 사용하고 Kotlin에서는 수명 주기 범위를 참조합니다. LiveData는 LifecycleOwner를 통해 구독을 자동으로 관리합니다. 구독은 Lifecycle이 STARTED 또는 RESUMED 상태일 때만 활성화됩니다. Compose의 StateFlow는 수명 주기를 인식하여 collectAsState()를 사용합니다. iOS의 NotificationCenter는 클로저가 self에 대한 강한 참조를 유지하므로 명시적인 [weak self]가 필요합니다.
Observer(GoF)와 발행자-구독자(PubSub) — 유사하지만 다른 패턴입니다. Observer에서 발행자는 구독자의 메서드를 직접 호출하여 알립니다. 발행자는 구독자를 알고 있습니다(목록 저장). PubSub에서 발행자와 구독자는 서로를 알지 못하며, 사이에 중개자(Event Bus, Message Queue, NotificationCenter)가 있습니다. 발행자는 채널에 메시지를 보내고 구독자는 채널을 수신합니다. PubSub은 더 느슨한 결합을 가집니다.
모바일 개발의 PubSub 예제 — iOS의 NotificationCenter는 PubSub으로 간주될 수 있습니다. 발행자는 구독자를 알지 못하고 단순히 알림을 게시합니다. Android의 EventBus 또는 Otto(더 이상 사용되지 않음). BroadcastChannel을 사용한 SharedFlow — Kotlin 세계의 PubSub. 분산 시스템에서 PubSub은 RabbitMQ, Kafka, Google PubSub을 통해 구현됩니다. 모바일 개발에서 PubSub은 모듈이 서로 의존해서는 안 되는 모듈식 아키텍처에서 유용합니다.
무엇을 선택할까 — UI 업데이트(ViewModel → View)에는 Observer(LiveData, StateFlow, @Published)를 사용합니다. 모듈 간 이벤트(인증, 로그아웃, 테마 변경)에는 PubSub(SharedFlow, NotificationCenter, EventBus)를 사용합니다. Observer는 단일 화면 내에서 더 간단하고 효율적이며, PubSub은 전역 이벤트에 더 유연하지만 암시적 종속성으로 인해 디버깅이 더 어렵습니다.
자주 묻는 질문
StateFlow는 Kotlin Coroutines의 플랫폼 독립적 유형이며, LiveData는 Android 수명 주기에 바인딩됩니다. StateFlow는 코루틴과 연산자를 지원하며 Android 없이 테스트할 수 있습니다. LiveData는 LifecycleOwner를 통해 구독을 자동으로 관리합니다. Google은 Kotlin + Compose를 사용하는 새 프로젝트에는 StateFlow를, Java 호환성에는 LiveData를 권장합니다.
핸들러 클로저에서 [weak self]를 사용하고 deinit에서 removeObserver()를 호출하세요. observer(NSObjectProtocol)에 대한 참조를 저장하고 객체가 소멸될 때 제거하세요. Combine에서는 DisposeBag이 해제될 때 자동 구독 취소를 위해 AnyCancellable과 store(in:)을 사용하세요.
네, SwiftUI는 @Published와 @StateObject/@ObservedObject와 함께 ObservableObject를 지원합니다. 이는 Observer의 내장 구현입니다. @Published는 자동으로 View에 변경 사항을 알립니다. Combine은 필수가 아닙니다. ObservableObject는 SwiftUI에 내장된 objectWillChange Publisher를 사용합니다. Combine은 스트림 변환을 위한 연산자를 추가합니다.
SharedFlow — 현재 값이 필요 없는 일회성 이벤트(탐색, 토스트, Snackbar)에 사용합니다. StateFlow — 현재 스냅샷이 필요한 UI 상태(데이터 목록, 로딩 진행률)에 사용합니다. SharedFlow에는 value 속성이 없으며 새 구독자에게 마지막 값을 반환하지 않습니다.
KVO는 이전 ObjC 메커니즘으로, @objc dynamic이 필요하며 NSObject에서 상속된 클래스에서만 작동합니다. Combine은 현대적인 Swift 프레임워크로, 타입 안전하며 연산자와 SwiftUI 통합을 제공합니다. Combine은 KVO와 NotificationCenter를 대체합니다. Apple은 새 프로젝트에는 Combine을, 레거시 지원에는 KVO만을 권장합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.