ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RxSwift - BehaviorSubject vs BehaviorRelay
    RxSwift 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

    'RxSwift' 카테고리의 다른 글

    RxSwift - Driver & BehaviorRelay  (0) 2025.02.06
    RxSwift로 서버 통신하기  (5) 2022.12.19
    RxSwift - Time Based Operator  (0) 2021.11.17
    RxSwift - Combining Operator  (0) 2021.11.12
    RxSwift - Transforming Operator  (0) 2021.11.10
Designed by Tistory.