개발콩블로그

[RxSwift] Cold-Hot Observable, Unicast와 Multicast 본문

RxSwift

[RxSwift] Cold-Hot Observable, Unicast와 Multicast

devBean 2025. 3. 1. 16:58

안녕하세요 개발콩입니다 !😊

RxSwift와 같은 기술스택은 알고 사용하는 것이 중요한 것 같습니다.

오늘은 Cold vs Hot ObservableUnicast vs Multicast에 대해서 알아보도록 하겠습니다.

 

구독 시점과 이벤트 방출 시점에 따라 달라지는 것이 Cold-Hot Observable입니다.

 

Cold Observable

  • 구독이 발생할 때까지 기다렸다가 이벤트를 방출합니다.
  • 처음부터 모든 데이터 stream을 확인할 수 있습니다. 따라서 각 subscriber가 구독을 시작한 시점에 동일한 데이터 stream을 받을 수 있습니다.

 

Cold Observable의 특성을 갖고있는 Observable 타입으로 예시를 확인해보겠습니다.

let coldObservable = Observable.just("추운 겨울이 가고 따뜻한 봄이 옵니다.")

coldObservable
    .subscribe { text in
        print(text)
    }
    .disposed(by: disposeBag)

// 추운 겨울이 가고 따뜻한 봄이 옵니다.

 

구독을 한 시점에 이벤트가 방출되고 해당 이벤트를 처리하는 것을 볼 수 있습니다.

 

 

 

Hot Observable

  • 구독 시점과는 상관없이 Observable 생성과 동시에 이벤트를 방출할 수 있습니다.
  • 처음부터 모든 데이터 stream을 확인하지 못할 수 있습니다. (데이터 stream을 중간부터 볼 수도 있습니다.)

 

Hot Observable의 특성을 갖고있는 Subject 타입으로 예시를 확인해보겠습니다.

 

let hotObservable = PublishSubject<String>()

hotObservable.onNext("추운 겨울이 가고 따뜻한 봄이 옵니다.")

hotObservable
    .subscribe { text in
        print(text)
    }
    .disposed(by: disposeBag)

hotObservable.onNext("벌써 뜨거운 여름이 왔습니다.")

// 벌써 뜨거운 여름이 왔습니다.

 

구독한 시점 이전에 방출된 "추운 겨울이 가고 따뜻한 봄이 옵니다." 이벤트는 확인하지 못합니다.

구독을 한 Observer가 없더라도 이벤트를 방출하는 것을 볼 수 있습니다.

 

즉, 구독한 시점 이전의 방출된 이벤트를 처리할 수 없고, 구독한 시점 이후의 방출된 이벤트만 처리하는 것을 확인할 수 있습니다.

 

 

 

UniCast

  • stream이 공유되지 않습니다.
  • 독립적인 stream을 갖습니다.

 

UniCast의 특징을 가진 Observable 타입으로 예시를 확인해보겠습니다.

let unicastObservable = Observable.create { observer in
    observer.onNext(Int.random(in: 1...100))
    return Disposables.create()
}

unicastObservable
    .subscribe { number in
        print("1번째 - \(number)") // 1번째 - next(27)
    }
    .disposed(by: disposeBag)

unicastObservable
    .subscribe { number in
        print("2번째 - \(number)") // 2번째 - next(48)
    }
    .disposed(by: disposeBag)

unicastObservable
    .subscribe { number in
        print("3번째 - \(number)") // 3번째 - next(30)
    }
    .disposed(by: disposeBag)

 

Unicast의 특성을 갖고있는 Observable을 생성합니다.

1부터 100까지의 random한 숫자를 방출합니다.

구독을 한 여러개의 stream이 모두 다른 값을 받는 것을 볼 수 있습니다.

 

즉, stream이 공유되지 않고 독립적인 stream을 갖는 것을 볼 수 있습니다.

 

 

 

MultiCast

  • stream이 여러 Observer에게 공유됩니다.
  • 모든 구독이 동일한 stream을 받게 됩니다.

 

MultiCast의 특성을 가진 Subject타입을 통해 예시를 확인해보겠습니다.

let multiCastObservable = BehaviorSubject(value: Int.random(in: 1...100))

multiCastObservable
    .subscribe { number in
        print("1번째 - \(number)") // 1번째 - next(75)
    }
    .disposed(by: disposeBag)

multiCastObservable
    .subscribe { number in
        print("2번째 - \(number)") // 2번째 - next(75)
    }
    .disposed(by: disposeBag)

multiCastObservable
    .subscribe { number in
        print("3번째 - \(number)") // 3번째 - next(75)
    }
    .disposed(by: disposeBag)

 

하나의 Observable에 대해서 여러 구독을 하더라도 동일한 값을 받는 것을 볼 수 있습니다.

모든 구독이 동일한 stream을 받는 것을 확인할 수 있습니다.

 

 

 

Share

share(replay, scope:)

Unicast로 동작하는 Stream을 MultiCast로 동작하도록 합니다.

  • replay
    • subscriber에 이전에 방출한 이벤트를 몇개까지 방출한 것인지 설정합니다.
  • scope
    • .whileConnected: subcriber가 1개 이상일 경우 replay 유지됩니다.
    • .forever: subcriber가 0개인 경우에도 replay가 유지됩니다.

 

 

share() method 사용 전

let tap = nextButton.rx.tap
    .map { Int.random(in: 1...100) }

tap
    .bind(with: self) { owner, value in
        print("1번 - \(value)") // 1번 - 44
    }
    .disposed(by: disposeBag)

tap
    .bind(with: self) { owner, value in
        print("2번 - \(value)") // 2번 - 70
    }
    .disposed(by: disposeBag)

tap
    .bind(with: self) { owner, value in
        print("3번 - \(value)") // 3번 - 94
    }
    .disposed(by: disposeBag)

 

 

share() method 사용 이후

let tap = nextButton.rx.tap
    .map { Int.random(in: 1...100) }
    .share()

tap
    .bind(with: self) { owner, value in
        print("1번 - \(value)") // 1번 - 55
    }
    .disposed(by: disposeBag)

tap
    .bind(with: self) { owner, value in
        print("2번 - \(value)") // 2번 - 55
    }
    .disposed(by: disposeBag)

tap
    .bind(with: self) { owner, value in
        print("3번 - \(value)") // 3번 - 55
    }
    .disposed(by: disposeBag)

 

 

share를 왜 사용할까?

let tap = nextButton.rx.tap
    .flatMap { 네트워크 통신 작업 }

위와 같이 버튼을 클릭하면 네트워크 통신 값을 변환하는 코드가 작성되어있다고 가정합니다.

 

이러한 Observable을 구독을하는 subscriber가 여러개가 존재한다면 그에 따른 개수마다 네트워크 통신을 반복 수행하게 될 것입니다.

우리는 이러한 경우에 share method를 적용해볼 수 있습니다.