ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RxSwift - subscribe vs bind vs drive
    RxSwift 2025. 2. 14. 11:19

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

    이번 포스팅에서는 RxSwift에서의 bind, subscribe, drive의 역할과 어떤 차이가 있는지에 대해 알아보겠습니다 🙋🏻

    오늘 정리해볼 이 subscribe, bind, drive는 각각 목적과 특징이 다르기에 적절히 사용하는것이 좋습니다.

     

    그렇기에 이 세가지에 대해 차이점을 확실히 알고 어떤 상황에서 어떤걸 사용할지 체크해보죠! 👯‍♂️

     

    그전에 하나 짚고 가볼께요.

     

    RxSwift에서의 핵심 개념은 Observable과 Observer죠.

    여기서 Observable은 데이터를 방출하는 스트림이고, Observer는 방출된 데이터를 수신하고 처리하는 객체 역할을 합니다.

    두 개념을 통해서 우리는 반응형 프로그래밍을 구현하는것이죠.

    여기서 subscribe, bind, drive가 이런 스트림을 다루는 방법 중 하나인거라 보면 좋을것 같아요 😃

     

    그럼 본격적으로 하나씩 알아보겠습니다!

     


    subscribe

    subscribe는 RxSwift의 가장 기본적인 Observer 연결 방식이에요.

    Observable의 이벤트를 감지하고 새로운 이벤트가 발생할 때 마다 클로저를 실행하죠.

    onNext, onError, onCompleted를 개별적으로 처리할 수 있게 됩니다.

     

    let disposeBag = DisposeBag()
    
    Observable.of(1, 2, 3)
        .subscribe(onNext: { value in
            print("Received value: \(value)")
        }, onError: { error in
            print("Error: \(error)")
        }, onCompleted: {
            print("Completed")
        })
        .disposed(by: disposeBag)

     

    위 예시코드처럼 사용될 수 있어요.

     

    subscribe는 다음과 같은 특징을 가집니다.

     

    1️⃣ 모든 Observable에서 사용 가능

    2️⃣ onNext, onError, onCompleted 핸들링 가능

    3️⃣ 에러 처리를 직접 해야함

    4️⃣ 메인 스레드를 보장하지 않아 UI 바인딩에 적합하지 않을 수 있음

     

    여기서 4번을 보면, 메인 스레드를 자동적으로 보장해주진 않아요.

    물론 아래처럼 observe로 메인 스케쥴러를 붙여줘서 보장해줄 순 있지만, 귀찮잖아요?ㅎㅎ

    그리고 실수할 여지가 있기에 UI 바인딩에 편리하게 적합하지 않다는 말입니다.

     

    let disposeBag = DisposeBag()
    
    Observable.of(1, 2, 3)
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { value in
            print("Received value: \(value)")
        }, onError: { error in
            print("Error: \(error)")
        }, onCompleted: {
            print("Completed")
        })
        .disposed(by: disposeBag)

     

    그럼 언제 이 subscribe를 사용할까요?

     

    1️⃣ 일반적인 데이터 스트림을 처리할 때

    2️⃣ API 호출 후 데이터를 가공하는 경우

    3️⃣ Rx 기반 이벤트 처리 시

     

    여기서 3번으로 말한 Rx 기반 이벤트 처리 예시는 다음과 같아요.

     

    let button = UIButton()
    let disposeBag = DisposeBag()
    
    button.rx.tap
        .subscribe(onNext: {
            print("Button was tapped")
        })
        .disposed(by: disposeBag)

     

    요렇게 버튼이 눌렸을때의 이벤트를 처리해줄때 사용되죠.

     

    그럼 이제 나아가서 bind를 살펴볼까요?

     


    bind

    bind는 주로 UI 컴포넌트와 Observable을 연결할 때 사용해줍니다.

    subscribe와 달리 onError 핸들러가 없기에, UI에서 발생할 수 있는 크래시를 방지하기 위해 자동으로 MainScheduler.instance에서 실행되게 해줍니다.

    만약 바인딩할 데이터 스트림에서 에러가 발생하면 앱이 크래시가 날 수 있기에 주의해야 해요.

    이를 방지하기 위해 catchErrorJustReturn(_:)을 활용하는것이 좋아요.

     

    즉, bind는 일반적인 Observable을 직접 UI 바인딩에 사용할 수 있도록 도와줘요.

     

    let textField = UITextField()
    let label = UILabel()
    let disposeBag = DisposeBag()
    
    textField.rx.text.orEmpty
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)
        
    let slider = UISlider()
    let label = UILabel()
    let disposeBag = DisposeBag()
    
    slider.rx.value
        .map { "\($0)" }
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)

     

    요렇게 사용된다고 볼 수 있습니다.

     

    bind의 특징을 볼까요?

     

    1️⃣ UI 바인딩에 최적화

    2️⃣ onError 핸들링 불가능

    3️⃣ 항상 메인 스레드에서 실행

    4️⃣ 일반적인 Observable을 UI와 연결

     

    그럼 언제 이 bind를 사용할까요?

     

    1️⃣ UI 요소와 데이터를 연결할 시

    2️⃣ UI 이벤트를 직접적으로 Observable과 연동할 때

    3️⃣ UI 스레드를 직접 관리하기 귀찮을때

     

    그럼 이제 마지막으로 drive에 대해 알아볼까요?

     


    drive

    drive는 bind와 비슷하지만, RxCocoa의 Driver와 함께 사용할 때 더 안전한 방법이에요.

    Driver는 에러 없이 항상 메인 스레드에서 동작하는 특징이 있기에 UI 바인딩을 더 안정적으로 수행할 수 있어요.

    또한, drive는 UI에서 예측하지 못한 에러가 발생해도 안전하게 동작합니다.

     

    let textField = UITextField()
    let label = UILabel()
    let disposeBag = DisposeBag()
    
    let textDriver = textField.rx.text.orEmpty.asDriver(onErrorJustReturn: "")
    textDriver
        .drive(label.rx.text)
        .disposed(by: disposeBag)

     

    요렇게 주로 사용됩니다.

     

    drive의 특징으론 아래와 같아요.

     

    1️⃣ bind와 유사하지만 Driver 타입만 허용

    2️⃣ onErrorJustReture(_:)을 사용해 에러 방지

    3️⃣ MainScheduler.instance에서 자동 실행

    4️⃣ UI 바인딩을 더욱 안전하게 수행 가능

     

    그럼 bind와 이 drive 어떤 차이를 가지는지 한번 정리해볼까요?

     


    bind vs drive

    구분 bind drive
    지원 타입 모든 Observable Driver
    에러 처리 불가능 (에러 발생 시 크래시 위험) 자동 처리 (onErrorJustReturn)
    스레드 메인 스레드 메인 스레드
    주요 용도 일반적인 UI 바인딩 더 안전한 UI 바인딩

     

    bind는 에러가 발생하면 스트림이 종료되기에 UI 업데이트가 중단될 가능성이 있어요.

    그렇기에 에러 처리를 명확히 해줘야 합니다.

    그말인즉슨, UI 바인딩이 지속적으로 보장되지 않는다는 소리랑 같다고 보입니다.

    비정상적인 상황에서는 예기치 못한 동작을 할수가 있죠.

     

    반면 drive는 조금 더 안전한 UI 바인딩이라고 볼 수 있습니다.

    에러가 발생해도 스트림이 종료되지 않고 UI 바인딩이 항상 보장됩니다.

    Driver 타입은 에러를 무시하고 .empty()로 처리하기에 안전하다고 볼 수 있어요.

     

    그렇기에 UI 바인딩을 위해서는 drive로 안전히 처리하는게 때에 따라 더 적절할것 같아요 ☺️

     

    그럼 조금 더 나아가서 언제 drive를 쓰고 언제 bind를 쓰는지 조금 더 정리해볼께요!

     


    bind와 drive 대체 언제?

    1️⃣ bind

    일반적인 Observable을 UI에 바인딩하는 경우에 사용되죠.

    즉, Driver 타입이 아닌 Completable, Single, Maybe와 같은 여타 Observable을 다루는 경우죠.

    그리고 UI 외에도 네트워크나 데이터 처리를 할 때도 사용할 수 있어요.

     

    2️⃣drive

    UI 업데이트가 반드시 보장되어야 하는 경우인 UIComponent에서 사용됩니다.

    에러가 발생해도 UI 바인딩이 끊기지 않도록 해야 하는 경우도 해당됩니다.

    주로 MVVM 패턴에서 UI 바인딩을 위해 Driver를 많이 사용하기에 이때 같이 접목되면 적절해요.

     

    물론, 아래와 같이 bind와 drive를 같이 사용해야 하는 경우도 있습니다.

     

    viewModel.username
        .drive(nameLabel.rx.text)
        .disposed(by: disposeBag)
    
    viewModel.errorMessage
        .bind { error in
            print("Error: \(error)")
        }
        .disposed(by: disposeBag)

     

    요렇게 username은 UI 업데이트를 보장해야 하기에 drive를 사용하게 되고, errorMessage는 UI 바인딩이 아니기에 bind를 사용할 수 있죠.

     

    그럼 지금까지 이 세가지에 대해 알아봤는데, 마지막으로 세가지를 비교 정리해보겠습니다 🔥

     


    subscribe vs bind vs drive

    구분 사용 대상 에러 처리 메인 스레드 보장 사용 사례
    subscribe 모든 Observable 가능 (onError) X 일반적 데이터 스트림 처리
    bind 모든 Observable 불가능 O UI 바인딩
    drive Driver 자동 처리 O UI 바인딩

     

    요렇게 정리해볼 수 있을것 같아요.

    즉, 대부분의 UI 바인딩을 한다면 bind나 drive를 사용하고 일반적인 비지니스 로직에서는 subscribe를 사용하게 됩니다.

    특히 Driver를 적극적으로 활용한다면 UI 관련 크래시를 방지하고 더욱 안정적인 Rx 코드를 작성할 수 있을거에요.

     


    마무리

    이렇게 차이를 인지하고 Rx 세계에서 적절한 연산자를 택하는것이 필요할것 같네요 😃

     


    레퍼런스

     

    RxSwift/RxCocoa/Common/Observable+Bind.swift at main · ReactiveX/RxSwift

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

    github.com

     

    RxSwift/RxCocoa/Traits/Driver/Driver+Subscription.swift at main · ReactiveX/RxSwift

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

    github.com

    'RxSwift' 카테고리의 다른 글

    Hot Observable vs Cold Observable  (1) 2025.02.11
    RxSwift - Driver & BehaviorRelay  (0) 2025.02.06
    RxSwift - BehaviorSubject vs BehaviorRelay  (0) 2025.02.03
    RxSwift로 서버 통신하기  (5) 2022.12.19
    RxSwift - Time Based Operator  (0) 2021.11.17
Designed by Tistory.