관리 메뉴

Kyung_Development

EventBus 본문

카테고리 없음

EventBus

Kyung_Development 2024. 12. 13. 09:15

이 글은 RxJava를 활용한 싱글톤 패턴의 EventBus 구현입니다. 주어진 코드는 여러 이벤트를 발행(Publish)하고 구독(Subscribe)할 수 있는 기능을 제공합니다. 아래는 주요 내용을 요약한 설명입니다.

 

 

구현 요소

  1. 싱글톤 패턴
    • companion object의 getInstance() 메서드를 통해 EventBus의 유일한 인스턴스를 생성 및 반환합니다.
    • synchronized 키워드를 사용하여 스레드 안전성을 보장합니다.
  2. 이벤트 관리
    • 각 이벤트마다 PublishSubject<Boolean> 인스턴스를 사용해 이벤트를 발행하고 구독할 수 있도록 구성하였습니다.
    • sendXxx 메서드는 이벤트를 발행하고, getXxx 또는 receiveXxx 메서드는 이벤트를 구독합니다.
  3. RxJava PublishSubject
    • PublishSubject는 새로운 구독자에게 과거 데이터를 전달하지 않고, 현재 발생하는 데이터 스트림만 전달합니다.
    • onNext 메서드로 이벤트 데이터를 발행합니다.
    • Observable 형태로 반환하여 구독자에게 이벤트 스트림을 제공합니다.
  4. 이벤트 종류
    • youtubeLoginFinishSubject: 유튜브 로그인 종료 상태 알림
    • loginSuccess: 로그인 성공 여부 알림
    • watchMissionSubject: 시청 미션 완료 여부 알림
    • participateMissionSubject: 참여 미션 완료 여부 알림
    • attendCompleteSubject: 출석 완료 여부 알림
    • inflmateAdRefreshSubject: 광고 갱신 이벤트 알림

 

 

사용 예시

// 발행
EventBus.getInstance().sendLoginSuccess(true)

// 구독
EventBus.getInstance().getLoginSuccess()
    .subscribe { isSuccess ->
        println("로그인 성공 여부: $isSuccess")
    }

 



장점

  • 모듈화: 여러 곳에서 공통 이벤트를 관리할 수 있습니다.
  • 비동기 처리: RxJava의 비동기 스트림 처리 기능을 활용해 UI와 백그라운드 작업 간의 효율적인 통신이 가능합니다.
  • 확장성: 새로운 이벤트를 쉽게 추가할 수 있습니다.


개선점

  1. Dispose 관리
    • 구독(Subscription) 관리를 하지 않을 경우 메모리 누수가 발생할 수 있습니다. 이를 방지하려면 CompositeDisposable을 활용해 구독을 관리하는 것이 좋습니다.
  2. Subject 남용 방지
    • PublishSubject는 여러 구독자가 있을 때 데이터 스트림을 관리하기 어렵습니다. BehaviorSubject나 ReplaySubject를 사용해 과거 데이터를 구독자에게 전달하는 방식도 고려할 수 있습니다.
  3. EventBus 대체
    • Android에서는 LiveData나 SharedFlow/StateFlow 같은 Jetpack Compose-friendly 기능을 사용해 이벤트를 관리하는 것도 좋습니다.
  4. 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의 호출 흐름

  1. 데이터를 발행:
    onNext(value)를 호출하면 값이 Observable로 전달됩니다.
  2. 데이터를 구독:
    구독자는 subscribe를 통해 onNext에서 발행된 데이터를 처리합니다.

어떻게 구독자는 데이터를 받나요?

구독자는 onNext로 전달된 데이터를 처리하기 위해 subscribe 메서드 내부에 로직을 정의합니다.

예:

val subject = PublishSubject.create<String>()

// 데이터 발행
subject.onNext("안녕하세요")  // 이벤트 전달
subject.onNext("RxJava 공부 중입니다")  // 이벤트 전달

// 데이터 구독
subject.subscribe { message ->
    println("수신된 메시지: $message")
}

 

 

출력 결과:

수신된 메시지: 안녕하세요
수신된 메시지: RxJava 공부 중입니다


주의할 점

  1. onNext는 구독자가 없더라도 호출 가능
    • 데이터를 발행하더라도 구독자가 없다면 이벤트는 소비되지 않고 무시됩니다.
  2. 비동기 작업에서 주로 사용
    • 네트워크 요청, 데이터 처리, 이벤트 발생 등 비동기 작업의 결과를 발행할 때 사용됩니다.
  3. 명확한 데이터 흐름 관리 필요
    • 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)은 클래스의 인스턴스를 단 하나만 생성하여 전역적으로 공유하도록 보장하는 디자인 패턴입니다.

이를 통해 시스템에서 동일 객체를 여러 번 생성하지 않고, 하나의 공유된 인스턴스를 재사용할 수 있습니다. 주로 공통 자원을 관리하거나, 설정값을 저장하는 데 사용됩니다.

 

특징

  1. 전역적 접근: 인스턴스는 전역적으로 접근 가능하며, 어디서든 동일한 객체를 사용할 수 있습니다.
  2. 단일 인스턴스 보장: 클래스가 여러 번 호출되더라도 항상 동일한 인스턴스를 반환합니다.
  3. 객체 재사용: 메모리 낭비를 줄이고, 상태를 공유할 수 있습니다.

싱글톤 패턴 구현 방법 (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")
    }
}

 


장점

  • 메모리 절약: 단일 인스턴스를 공유하여 메모리 사용을 최적화.
  • 전역 상태 관리: 인스턴스를 어디서든 접근 가능.
  • 객체 생성 비용 감소: 동일한 객체를 재사용.

단점

  • 글로벌 상태 관리의 위험: 상태가 많아질수록 관리가 어려워질 수 있음.
  • 멀티스레드 문제: 동기화 처리가 제대로 되지 않으면 예상치 못한 동작 발생 가능.
  • 테스트 어려움: 단일 인스턴스의 특성 때문에 테스트에서 객체 상태를 초기화하기 어렵다.

결론

싱글톤 패턴은 특정 자원의 공유전역 관리가 필요한 경우 유용하지만, 무분별하게 사용하면 코드의 결합도를 높이고, 테스트를 어렵게 만들 수 있습니다. 적재적소에 신중히 사용하는 것이 중요합니다.