| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 시퀀스다이어그램
- 구조패턴
- decoding
- 전역상태관리
- SDLC
- android
- MVVM
- 행위패턴
- scaletype
- SharedFlow
- LazyInitialization
- LayeredArchitecture
- stateflow
- threadsafety
- Kotlin
- 클래스다이어그램
- 유스케이스다이어그램
- 이미지찌그러짐
- RxJava
- 아키텍쳐패턴
- NetworkCommunication
- ClientServerArchitecture
- ArchitecturePatterns
- 이미지짤림
- ImageView
- DistributedSystems
- 옵서버
- 싱글톤패턴
- SystemDesign
- SoftwareArchitecture
- Today
- Total
Kyung_Development
EventBus 본문
이 글은 RxJava를 활용한 싱글톤 패턴의 EventBus 구현입니다. 주어진 코드는 여러 이벤트를 발행(Publish)하고 구독(Subscribe)할 수 있는 기능을 제공합니다. 아래는 주요 내용을 요약한 설명입니다.
구현 요소
- 싱글톤 패턴
- companion object의 getInstance() 메서드를 통해 EventBus의 유일한 인스턴스를 생성 및 반환합니다.
- synchronized 키워드를 사용하여 스레드 안전성을 보장합니다.
- 이벤트 관리
- 각 이벤트마다 PublishSubject<Boolean> 인스턴스를 사용해 이벤트를 발행하고 구독할 수 있도록 구성하였습니다.
- sendXxx 메서드는 이벤트를 발행하고, getXxx 또는 receiveXxx 메서드는 이벤트를 구독합니다.
- RxJava PublishSubject
- PublishSubject는 새로운 구독자에게 과거 데이터를 전달하지 않고, 현재 발생하는 데이터 스트림만 전달합니다.
- onNext 메서드로 이벤트 데이터를 발행합니다.
- Observable 형태로 반환하여 구독자에게 이벤트 스트림을 제공합니다.
- 이벤트 종류
- youtubeLoginFinishSubject: 유튜브 로그인 종료 상태 알림
- loginSuccess: 로그인 성공 여부 알림
- watchMissionSubject: 시청 미션 완료 여부 알림
- participateMissionSubject: 참여 미션 완료 여부 알림
- attendCompleteSubject: 출석 완료 여부 알림
- inflmateAdRefreshSubject: 광고 갱신 이벤트 알림
사용 예시
// 발행
EventBus.getInstance().sendLoginSuccess(true)
// 구독
EventBus.getInstance().getLoginSuccess()
.subscribe { isSuccess ->
println("로그인 성공 여부: $isSuccess")
}
장점
- 모듈화: 여러 곳에서 공통 이벤트를 관리할 수 있습니다.
- 비동기 처리: RxJava의 비동기 스트림 처리 기능을 활용해 UI와 백그라운드 작업 간의 효율적인 통신이 가능합니다.
- 확장성: 새로운 이벤트를 쉽게 추가할 수 있습니다.
개선점
- Dispose 관리
- 구독(Subscription) 관리를 하지 않을 경우 메모리 누수가 발생할 수 있습니다. 이를 방지하려면 CompositeDisposable을 활용해 구독을 관리하는 것이 좋습니다.
- Subject 남용 방지
- PublishSubject는 여러 구독자가 있을 때 데이터 스트림을 관리하기 어렵습니다. BehaviorSubject나 ReplaySubject를 사용해 과거 데이터를 구독자에게 전달하는 방식도 고려할 수 있습니다.
- EventBus 대체
- Android에서는 LiveData나 SharedFlow/StateFlow 같은 Jetpack Compose-friendly 기능을 사용해 이벤트를 관리하는 것도 좋습니다.
- Generic 활용
- 이벤트 데이터 타입이 모두 Boolean으로 고정되어 있습니다. 이를 제네릭으로 변경하면 더 유연한 설계를 구현할 수 있습니다.
class EventBus<T> private constructor() {
private val subject: PublishSubject<T> = PublishSubject.create()
fun send(event: T) {
subject.onNext(event)
}
fun get(): Observable<T> {
return subject
}
}
onNext는 RxJava에서 데이터를 발행(publish) 하기 위해 사용되는 메서드입니다. Observable이 구독자(Subscriber) 또는 옵저버(Observer)에게 데이터를 전달하려면 onNext를 호출하여 데이터를 보냅니다.
onNext의 역할
- 데이터를 스트림으로 발행합니다.
- 구독자는 onNext를 통해 전달된 데이터를 받을 수 있습니다.
- 이벤트를 발생시키는 역할로, 스트림에 데이터를 추가하는 것과 같습니다.
onNext를 언제 사용해야 하나요?
onNext는 이벤트가 발생했거나, 데이터를 전달하고 싶을 때 호출해야 합니다.
예를 들어, 특정 작업이 완료되었음을 알리거나, UI를 갱신하기 위해 값을 전달할 때 사용합니다.
예시: 버튼 클릭 이벤트 전달
val clickSubject = PublishSubject.create<Boolean>()
// 이벤트 발행
clickSubject.onNext(true) // 버튼이 클릭되었음을 알림
// 이벤트 구독
clickSubject.subscribe { isClicked ->
if (isClicked) {
println("버튼이 클릭되었습니다!")
}
}
onNext의 호출 흐름
- 데이터를 발행:
onNext(value)를 호출하면 값이 Observable로 전달됩니다. - 데이터를 구독:
구독자는 subscribe를 통해 onNext에서 발행된 데이터를 처리합니다.
어떻게 구독자는 데이터를 받나요?
구독자는 onNext로 전달된 데이터를 처리하기 위해 subscribe 메서드 내부에 로직을 정의합니다.
예:
val subject = PublishSubject.create<String>()
// 데이터 발행
subject.onNext("안녕하세요") // 이벤트 전달
subject.onNext("RxJava 공부 중입니다") // 이벤트 전달
// 데이터 구독
subject.subscribe { message ->
println("수신된 메시지: $message")
}
출력 결과:
수신된 메시지: 안녕하세요
수신된 메시지: RxJava 공부 중입니다
주의할 점
- onNext는 구독자가 없더라도 호출 가능
- 데이터를 발행하더라도 구독자가 없다면 이벤트는 소비되지 않고 무시됩니다.
- 비동기 작업에서 주로 사용
- 네트워크 요청, 데이터 처리, 이벤트 발생 등 비동기 작업의 결과를 발행할 때 사용됩니다.
- 명확한 데이터 흐름 관리 필요
- onNext 호출 위치가 많아지면 데이터 흐름이 복잡해질 수 있으니, 명확한 이벤트 트리거 설계가 중요합니다.
정리
- onNext는 데이터를 스트림으로 발행하는 메서드입니다.
- "언제": 이벤트를 전달하거나 데이터를 다른 부분으로 보낼 때 사용합니다.
- "어떻게": onNext(value)로 데이터를 발행하고, subscribe로 데이터를 구독하여 처리합니다.
SharedFlow와 StateFlow는 Kotlin의 Coroutines Flow에서 제공하는 기능으로, 데이터 스트림을 여러 구독자와 공유하거나, 상태를 관리하기 위해 사용됩니다. 이들은 RxJava의 Subject와 유사하지만, 코루틴 컨텍스트에서 더 자연스럽게 작동합니다.
1. SharedFlow
특징
- SharedFlow는 공유 가능한 데이터 스트림입니다.
- 여러 구독자가 동시에 동일한 데이터를 받을 수 있습니다.
- 새로운 구독자는 과거 데이터를 받지 않고, 발행된 이후의 데이터만 구독합니다.
- 기본적으로 Replay 기능을 설정해 과거 데이터를 일정 개수만 보관할 수도 있습니다.
사용 예시
버튼 클릭 이벤트를 공유하는 경우:
val sharedFlow = MutableSharedFlow<Boolean>() // 공유 가능한 Flow 생성
// 이벤트 발행
fun onButtonClick() {
sharedFlow.emit(true) // 버튼이 클릭됨을 알림
}
// 이벤트 구독
fun observeButtonClick() {
sharedFlow.collect { isClicked ->
println("버튼 클릭 이벤트 발생: $isClicked")
}
}
특징 정리:
- Broadcast 방식: 모든 구독자에게 동일한 데이터 전달.
- Replay 설정: replay를 통해 과거 이벤트를 새 구독자에게 전달 가능.
예: MutableSharedFlow(replay = 1)로 설정 시, 마지막 1개의 데이터만 새 구독자에게 전달. - Hot Stream: 구독자가 없을 때 데이터를 발행하면 그 데이터는 사라집니다.
2. StateFlow
특징
- StateFlow는 상태를 저장하고 관리하기 위한 데이터 스트림입니다.
- 항상 하나의 최신 상태를 유지하며, 새로운 구독자는 최신 상태를 즉시 받습니다.
- LiveData와 비슷하지만, 코루틴 기반으로 더 유연하게 사용할 수 있습니다.
사용 예시
로그인 상태를 관리하는 경우:
val loginStateFlow = MutableStateFlow(false) // 초기값 설정 (로그인 안됨)
// 상태 변경
fun setLoginState(isLoggedIn: Boolean) {
loginStateFlow.value = isLoggedIn // 상태 업데이트
}
// 상태 구독
fun observeLoginState() {
loginStateFlow.collect { isLoggedIn ->
println("로그인 상태: $isLoggedIn")
}
}
특징 정리:
- Latest Value: 항상 최신 값을 저장.
- Cold Stream: 구독자가 없더라도 항상 상태를 유지.
- LiveData 대체 가능: Android 프로젝트에서 LiveData를 대체하기 적합.
SharedFlow와 StateFlow 비교
| 특성 | SharedFlow | StateFlow |
| 목적 | 이벤트 스트림 공유 | 상태 관리 |
| 초기값 필요 여부 | 필요 없음 | 필요함 (항상 초기값 존재) |
| Replay 기능 | 지원 (기본 0) | 미지원 |
| Hot vs Cold | Hot Stream (구독자 없으면 데이터 소실) | Cold Stream (항상 상태 유지) |
| LiveData 대체 | 적합하지 않음 | 적합 |
3. 언제 사용해야 할까?
SharedFlow 사용 사례:
- 이벤트 기반 데이터 처리
- 클릭 이벤트, 알림 등 특정 이벤트를 여러 곳에서 동시에 처리해야 할 때.
- RxJava의 PublishSubject나 BehaviorSubject의 대안.
StateFlow 사용 사례:
- 상태 기반 데이터 관리
- 로그인 상태, UI 상태 등 지속적으로 관찰해야 하는 상태를 관리할 때.
- LiveData를 대체하고 싶을 때.
4. Lifecycle과 함께 사용하기
Android 프로젝트에서 SharedFlow와 StateFlow는 LifecycleScope와 함께 사용하면 더 효율적으로 관리할 수 있습니다.
예를 들어, StateFlow를 repeatOnLifecycle로 구독하여 메모리 누수를 방지합니다.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
stateFlow.collect { state ->
// UI 업데이트
}
}
}
결론
- **SharedFlow**는 이벤트 전달에, **StateFlow**는 상태 관리에 적합합니다.
- 기존의 LiveData나 RxJava를 대체할 수 있으며, 코루틴과 함께 사용할 때 더 유연하고 간결한 코드 작성을 도와줍니다.
싱글톤 패턴(Singleton Pattern)은 클래스의 인스턴스를 단 하나만 생성하여 전역적으로 공유하도록 보장하는 디자인 패턴입니다.
이를 통해 시스템에서 동일 객체를 여러 번 생성하지 않고, 하나의 공유된 인스턴스를 재사용할 수 있습니다. 주로 공통 자원을 관리하거나, 설정값을 저장하는 데 사용됩니다.
특징
- 전역적 접근: 인스턴스는 전역적으로 접근 가능하며, 어디서든 동일한 객체를 사용할 수 있습니다.
- 단일 인스턴스 보장: 클래스가 여러 번 호출되더라도 항상 동일한 인스턴스를 반환합니다.
- 객체 재사용: 메모리 낭비를 줄이고, 상태를 공유할 수 있습니다.
싱글톤 패턴 구현 방법 (Kotlin)
1. 객체(Object)로 구현
Kotlin에서는 object 키워드로 간단히 싱글톤을 구현할 수 있습니다.
object는 클래스와 동시에 인스턴스를 생성하며, 이를 통해 싱글톤을 보장합니다.
예시: Logger 싱글톤
object Logger {
fun log(message: String) {
println("Log: $message")
}
}
// 사용 예시
Logger.log("Hello, Singleton!") // 항상 동일 객체 사용
2. Lazy Initialization (지연 초기화)
필요할 때 인스턴스를 생성하는 방식입니다. Kotlin에서는 by lazy를 사용하여 구현할 수 있습니다.
예시: Lazy 초기화
class Singleton private constructor() {
companion object {
val instance: Singleton by lazy { Singleton() }
}
}
// 사용 예시
val singleton = Singleton.instance
3. Thread-safe Singleton
싱글톤 패턴은 멀티스레드 환경에서 동기화 문제가 발생할 수 있습니다.
Kotlin에서는 synchronized를 사용하여 스레드 안전성을 보장할 수 있습니다.
예시: Thread-safe Singleton
class ThreadSafeSingleton private constructor() {
companion object {
@Volatile
private var instance: ThreadSafeSingleton? = null
fun getInstance(): ThreadSafeSingleton {
return instance ?: synchronized(this) {
instance ?: ThreadSafeSingleton().also { instance = it }
}
}
}
}
// 사용 예시
val singleton = ThreadSafeSingleton.getInstance()
싱글톤 패턴 활용 사례
1. 전역 상태 관리
- 앱 설정값, 캐싱 데이터 등을 관리하는 데 사용.
object AppConfig {
var theme: String = "Light"
var language: String = "en"
}
2. 공유 자원 관리
- 데이터베이스 연결, 네트워크 요청 클라이언트, 파일 시스템 접근 등.
object DatabaseManager {
fun connect() {
println("Database Connected")
}
}
3. 로그 관리
- 애플리케이션의 로그를 중앙에서 관리.
object LogManager {
fun log(message: String) {
println("Log: $message")
}
}
장점
- 메모리 절약: 단일 인스턴스를 공유하여 메모리 사용을 최적화.
- 전역 상태 관리: 인스턴스를 어디서든 접근 가능.
- 객체 생성 비용 감소: 동일한 객체를 재사용.
단점
- 글로벌 상태 관리의 위험: 상태가 많아질수록 관리가 어려워질 수 있음.
- 멀티스레드 문제: 동기화 처리가 제대로 되지 않으면 예상치 못한 동작 발생 가능.
- 테스트 어려움: 단일 인스턴스의 특성 때문에 테스트에서 객체 상태를 초기화하기 어렵다.
결론
싱글톤 패턴은 특정 자원의 공유나 전역 관리가 필요한 경우 유용하지만, 무분별하게 사용하면 코드의 결합도를 높이고, 테스트를 어렵게 만들 수 있습니다. 적재적소에 신중히 사용하는 것이 중요합니다.