RxSwift

RxSwift - BehaviorSubject vs BehaviorRelay

GREEN.1229 2025. 2. 3. 14:25

안녕하세요. 그린입니다 🍏

이번 포스팅은 RxSwift에서 BehaviorSubject와 BehaviorRelay의 차이에 대해 학습해보겠습니다 🙋🏻

 

RxSwift를 사용하여 상태를 관리할 때 두 개념 모두 자주 사용되는데요.

두가지 모두 Subject입니다.

 

어떤 차이를 가지고 있고, 어떤 상황에서 어떤것을 쓰는게 좋을지에 대해 알아볼께요.


BehaviorSubject

BehaviorSubject는 현재 값을 유지하며, 새롭게 구독하는 Subscriber에게 항상 최신 값을 방출하는 Subject입니다.

또한 초기 및 기존 값이 존재하기 때문에 UI 상태 관리 시 유용하게 사용될 수 있죠.

 

특징으로는 다음과 같습니다.

 

1️⃣ 초기값 지정

2️⃣ 구독 시 최신 값을 즉시 수신

3️⃣ .onNext(value), .onError(error), onCompleted() 호출 가능

4️⃣ .error 혹은 .completed 시 추가적인 이벤트 방출 없음

 

한번 사용 예시를 간단히 볼까요?

 

import RxSwift

let disposeBag = DisposeBag()
let subject = BehaviorSubject(value: "초기값")

subject.subscribe { event in
    print("Subscriber 1: \(event)")
}.disposed(by: disposeBag)

subject.onNext("새로운 값")

subject.subscribe { event in
    print("Subscriber 2: \(event)")
}.disposed(by: disposeBag)

// Subscriber 1: next(초기값)
// Subscriber 1: next(새로운 값)
// Subscriber 2: next(새로운 값)

 

이렇게 Subscriber가 두개 존재하고 subject를 모두 옵저빙해요.

이때 출력 결과를 보면, Subscriber2가 구독 시 최신 존재하는 2의 값을 받는것이죠.

 

이와 비슷한 BehaviorRelay를 살펴볼까요?

 


BehaviorRelay

BehaviorRelay는 BehaviorSubject를 래핑한 형태로 RxCocoa에서 제공합니다.

큰 차이점으로는 .onError()와 .onCompleted()를 지원하지 않는다는 것이죠.

이 차이점은 UI 바인딩에 더 적합하도록 설계된것이에요.

그렇기에 RxCocoa에서 제공하겠죠!?

 

특징으로는 다음과 같아요.

 

1️⃣ .onError(), .onCompleted() 없음

2️⃣ 내부적으로 BehaviorSubject를 사용하지만, 구독이 종료되지 않음

3️⃣ .onNext(value) 대신, .accept(value)를 사용해 새 값 방출

4️⃣ UI 상태 관리를 위한 최적의 선택

 

사용 예시도 볼까요?

 

import RxSwift
import RxCocoa

let disposeBag = DisposeBag()
let relay = BehaviorRelay(value: "초기값")

relay.subscribe { event in
    print("Subscriber 1: \(event)")
}.disposed(by: disposeBag)

relay.accept("새로운 값")

relay.subscribe { event in
    print("Subscriber 2: \(event)")
}.disposed(by: disposeBag)

// Subscriber 1: next(초기값)
// Subscriber 1: next(새로운 값)
// Subscriber 2: next(새로운 값)

 

BehaviorSubject와 사용과 흐름이 동일하죠?

 

그럼 이걸 일목요연하게 정리해서 BehaviorSubject와 BehaviorRelay는 어떤 차이가 있을까요?

 


BehaviorSubject vs BehaviorRelay

특성 BehaviorSubject BehaviorRelay
초기값 설정 O O
최신값 제공 O O
.onNext 호출 여부 O X (.accept 사용)
.onError 지원 O X
.onCompleted 지원 O X
UI 바인딩 적합성 ? (에러 시 종료) O

 

여기서 살펴볼것이 UI 바인딩 적합성인데요.

BehaviorSubject는 물음표로 두었어요.

MVVM에서 ViewModel의 데이터 바인딩 시 BehaviorRelay는 절대 종료되지 않는 스트림을 제공하기에 UI 바인딩에 매우 적합해요.

(왜냐하면, completed, errorr가 없어서 스트림이 강제 종료되지 않습니다.)

 

예를들어, 테이블 뷰 사용을 볼 수 있는데요.

BehaviorRelay로 보다 안전하게 값 업데이트가 가능합니다.

만약 BehaviorSubject를 사용해 한 번 에러가 발생하거나 완료되면 데이터를 갱신할 수 없는 상태가 될 위험이 있어요.

그래서, BehaviorSubject를 물음표로 두었어요.

반면, 다시 언급하지만 BehaviorRelay는 종료 개념 자체가 없기에 항상 안전하게 데이터 업데이트가 가능합니다.

 

// BehaviorSubject
let subject = BehaviorSubject(value: ["초기 데이터"])

// onError를 호출하면 이후 데이터 바인딩이 중단됨
subject.onError(NSError(domain: "테스트 오류", code: -1, userInfo: nil))

subject.subscribe(onNext: { data in
    print("데이터 업데이트: \(data)")
}).disposed(by: disposeBag)

// 새 데이터 추가
subject.onNext(["새로운 데이터"]) // ❌ 에러로 인해 실행되지 않음

// BehaviorRelay
let relay = BehaviorRelay(value: ["초기 데이터"])

// 새로운 데이터 추가
relay.accept(["새로운 데이터"]) // ✅ 정상적으로 UI 업데이트됨

 

accept도 내부적으로 onNext를 호출하지만 onCompleted나 onError를 막아둔 안전한 방식을 취하고 있어요.

BehaviorRelay를 이용해 테이블 뷰와 아래와 같이 안전히 연결해주죠.

 

let dataRelay = BehaviorRelay(value: ["초기 데이터"])

// 테이블뷰와 바인딩
dataRelay.bind(to: tableView.rx.items(cellIdentifier: "cell")) { _, item, cell in
    cell.textLabel?.text = item
}.disposed(by: disposeBag)

// 데이터 추가
dataRelay.accept(dataRelay.value + ["새로운 데이터"]) // ✅ UI 업데이트 정상 동작

 

결과적으로 테이블뷰의 데이터는 동적으로 변경되며, 새로운 데이터가 추가될 때 UI가 자동으로 업데이트되어야 하는것이 중요해요.

이때 BehaviorRelay는 스트림이 종료되지 않고, 값이 변경될 때만 UI를 갱신하기에 테이블뷰에서 사용하기가 적합하죠.

 

이 외에도 BehaviorRelay는 싱글턴 패턴과 결합해 앱 전역적인 상태를 관리할 때도 사용하기 유용할 수 있습니다.

 

그럼 정리해서 언제 어떤것을 적절히 사용하는게 좋을까요?

 


When use it?

여러가지 기준이 있을 수 있겠지만 크게 네가지로 잡아봤습니다.

 

1️⃣ UI 상태 관리 위주 - BehaviorRelay

2️⃣ 상태 변경 감지가 필요하지만, 에러 및 완료 이벤트는 불필요 - BehaviorRelay

3️⃣ 특정 시점에 Subject 종료되어야 함 - BehaviorSubject

4️⃣ Observable이 에러를 방출해야 함 - BehaviorSubject

 

이런것처럼 대부분의 UI 바인딩에서는 사실상 BehaviorRelay를 사용하면 되지만, 스트림을 종료해야 하거나 에러 및 완료 처리를 구분받아 이에 적절히 UI 처리를 해야한다면 BehaviorSubject를 사용하는것이 적절해보입니다 😃

 


마무리

두 가지 모두 상태를 관리하는 훌륭한 도구입니다.

이에 프로젝트의 요구사항에 맞춰 적절한 도구를 선택하는것이 개발자의 몫일것 같네요!

 


레퍼런스

 

ReactiveX - Subject

만약, Subject를 정의했는데, 이를 Subscriber 인터페이스 없이 다른 에이전트에 전달하고 싶다면 그 Subject를 순수 Observable로 리턴하는 asObservable 메서드를 사용하면 된다. 참고

reactivex.io

 

 

RxSwift/RxRelay/BehaviorRelay.swift at main · ReactiveX/RxSwift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com