티스토리 뷰
[RxSwift] RxSwift 기본 개념 파헤치기 (Subscribe, Bind, Subject, Relay, Drive)
yanni13 2026. 2. 24. 01:52
오늘은 RxSwift를 사용할 때 사용되는 프로퍼티들에 대해서 알아보고자 한다!
(제가 공부하기 위해 작성한 것이므로 여러 개념들이 나올 수 있으며 틀린 정보가 있다면 알려주세요!)
RxSwift의 기본 컨셉이 되는 Observable, Observer, Dispose에 대한 이야기는 이전에 작성해둔 블로그도 있기 때문에 오늘은 프로퍼티에 대해서 더 자세히 알아보고자 한다.
1️⃣ Subscribe
RxSwift는 기본적으로 이벤트를 전달하는 Observable과 이벤트를 처리하는 Observer로 구성되어 있다.
(Observable과 Observer에 대한건 이전 포스팅에도 있기 때문에 이번 포스팅에서는 짧게 다루고 넘어가게보겠다!)
Observable은 이벤트를 전달하는 입장이고 Observer는 그 이벤트를 계속해서 받는 입장이다.
예를 들어 버튼을 클릭했을때 버튼을 클릭했다는 신호 혹은 셀을 클릭하거나 검색어를 입력했을때 이벤트를 계속해서 전달하고 있는게 Observable이고 그 이벤트를 받아서 레이블을 표시하고 화면전환을 통해 검색 결과를 보여주는 역할을 Observer가 하게 되는 것이다.
이때 계속해서 observable이 전달하는 이벤트를 Observer에게 전달해줄 무언가가 필요한데 둘 사이를 연결해주는 역할이 바로 Subscribe이다!

subscribe는 onNext, onComplete, OnError, onDisposed 4개에 대한 이벤트를 처리할 수 있다.
Observable
.just("중복확인")
.subscribe { value in // OnNext
print("next: \(value)")
self.navigationItem.title = value
self.nextButton.setTitle(value, for: .normal)
} onError: { error in
print("error: \(error)")
} onCompleted: {
print("completed")
} onDisposed: {
print("disposed")
}
.disposed(by: disposeBag)
- onNext: subscribe는 next 이벤트를 통해서 이벤트를 방출하는 특성을 가지고 있다. ( onNext는 Trailing Closure의 특성을 가지고 있어서 첫 번째 파라미터인 onNext 라벨이 생략된 것이다!)
- onError: Observable이 이벤트가 실행중 Lifecycle 중 오류가 발생한다면 error 이벤트를 방출
- onCompleted: 정상적으로 모든 이벤트가 완료된다면 completed를 Notification 으로 안내
- onDisposed: deinit 되는 시점이나 Sequence가 종료되는 시점에 disposed가 실행
error 또는 completed를 전달받으면 해당 Sequence가 종료되고, 종료된 이후에는 더 이상 이벤트가 발생하지 않는다. Observable은 내부에 상태를 가지는데 한 번 종료 상태로 넘어가면, 어떤 값을 넣어도 다시 Active 상태로 돌아오지 않기 때문에 Observable에 대한 재사용이 불가능하게 된다.
subscribe는 당연하게도 이벤트를 전달하는 역할이기 때문에 observable이나 observer가 하나라도 없다면 존재할 수 없게 된다.
📍label.rx.text 에는 subscribe 가 되지 않는 이유?
라벨은 무한한 이벤트이기 때문에 오류가 절대 발생하지 않기 때문이다. TV에 빗대어서 설명해보자면 Observable은 전기를 보내고 있는데 label.rx.text는 단순히 TV에 어떤 화면을 보여줄지 표시하는 역할이다.
즉 성립될 수 없는 이유는 subscribe 는 데이터를 주면 연결해주겠다는 명령인데 label은 데이터를 주는 애가 아니라 받는 애인 Observer이기 때문에 subscribe가 되지 않는다.
이외에도 label을 표시하는게 실패할 일이 없기 때문에 에러가 발생하지 않고 onCompleted도 이벤트가 완벽하게 끝이 났을 경우에 실행되는 건데 label은 무한한 이벤트이며 끝난다는 개념이 없기 때문에 실행되지 않는다.
disposed 측면에서 한번 살펴보자!
Observable에서 next 이벤트에 대한 emit이 끝나면, completed → disposed로 Sequence가 정상적으로 종료되고, error가 와도 error 이벤트에 대해 방출이 되면 error → disposed로 Sequence가 정상적으로 종료된다.
하지만 무한한 이벤트를 가지는 버튼이나 라벨과 같이 무한히 emit이 될 수 있는 상황에서는, 버튼을 100번 누르든 1000번 누르든 사용자가 앱을 끄기 전까진 이벤트가 계속 실행될 수 있기 때문에 disposed 또한 되지 않는다.
그래서 disposeBag을 통해 강제 종료 해주는 시점이 필요한 것이다!
2️⃣ Bind
bind는 subscribe랑 동일하게 Observable와 Observer를 연결해주는 역할을 한다.
Observable과 Observer를 연결하도록 구독해서 계속 관찰하고 있다가 값이 변경됨을 감지하면 다음 이벤트를 어떻게 처리할 지 명시해줘야 한다.
앞서 말한 버튼과 같은 무한한 이벤트에서는 onCompleted가 실행될 일도, onError가 실행될 일도 없기 때문에 클로저에 의미없는 print를 작성하거나 비워둘 빠에는 사용하지 않는 편이 낫다.
따라서 우리는 subscribe에서 4가지 이벤트 클로저에서 onNext만 사용해야지!라고 할수도 있지만 그마저 불필요한 리소스나 공간을 차지하지 않도록 하기 위해 사용하는 것이 bind이다!

bind는 매개변수가 onNext에 대한 이벤트밖에 처리할 수 없다.
.bind { value in
print("실시간 검색", value)
}
따라서 bind를 사용해서 ui코드도 아래와 같이 구성해볼 수 있게 된다.
placeholder
.bind(to: emailTextField.rx.placeholder)
.disposed(by: disposeBag)
프로퍼티 안에 placeholder 색상을 직접 지정할 수 있지만 rx를 더 적극적으로 사용해보고자 했을땐 위의 방법으로 구독형식을 사용해서 viewDidLoad안에서 사용해볼 수도 있을거 같다!


실제로 RxSwift 라이브러리를 다운받아서 코드를 살펴보았을때도 프로퍼티에 직접 값을 할당하는 방식이 아닌 스트림을 통해 상태를 주입하는 방식으로 테스트 코드를 작성하고 있었다.
RxSwift를 사용해보고자 할 때 코드의 통일성을 위해서 뷰까지 이렇게 구현해볼 수도 있겠지만, 내부로직에서 가질 수 있는 특징은 view의 초기화 시점에서도 구독 형태는 viewDidLoad가 호출될 때 마다 구독이 생성되고 Observable이 값을 방출할 때 마다 반응을 한다.
그리고 특정 시점에 deinit이 되는 방식이지만, 프로퍼티로 값에 접근하는 방식은 viewDidLoad시점에 뷰에 딱 한번 로드되어 아무 변화가 일어나지 않기 때문에 생명주기 특성이 달라질 수 있다는 특징이 있다.
즉 값이 동적으로 자주 변할 때 구독을 사용하면 좋을거 같고, 고정값이 들어가야 한다 하면 직접 할당 방식이 자원을 해제 하는 관점에서 더 적은 비용이 들 수 있을거 같다!
3️⃣ Subject
이벤트 전달과 처리를 둘 다 할 수 있음
앞에서 subscribe와 bind는 Observable 타입이기 때문에 항상 이벤트 전달만 할 수 있고 그에 대한 처리는 할 수 없었다. 하지만 subject는 이벤트 전달과 처리를 둘 다 할 수 있다.
subject타입으로 사용할 때는 다음 이벤트를 어떻게 수행해라고 할 때 onNext를 사용해야 한다.
Subject의 종류로는 아래와 같이 4가지의 종류가 존재한다.
(그중에서 오늘은 제일 많이 사용하는 BehaviorSubject와 PublishSubject에 대해서 알아보고자 한다!)
- BehaviorSubject : 생성될 때 초기값이 필수이기 때문에 subscribe 이전에 방출한 이벤트가 없다면 뷰를 미리 채워두기용으로 적절하다.
- PublishSubject: 초기값이 없는 빈 상태로 시작한다. subscribe 이후 시점에서 방출되는 이벤트를 처리할 수 있는 특성을 가졌기 때문에 구독 이전에 방출된 이벤트들은 적용되지 않는다.
- ReplaySubject
- AsyncSubject
아래 코드로 살펴보자!
private let name = BehaviorSubject(value: "홍길동")
...
validationButton
.rx
.tap
.bind(with: self) { owner, _ in
let list = ["홍길동", "김철수", "김영희"]
let random = list.randomElement()!
// 다음 이벤트에 대한 처리를 onNext로 실행
owner.name.on(.next(random))
owner.name.onNext(random)
owner.emailTextField.text = random
}
.disposed(by: disposeBag)
BehaviorSubject를 생성할 때는 초기값을 반드시 작성해줘야 한다. 따라서 특정 값을 초기값으로 반드시 가지고 있어야 하는 상태에 주로 사용을 해주었다.
BehaviorSubject는 Subscribe 이전에 방출한 이벤트가 있다면 가장 최근에 전달된 이벤트 하나를 전달받을 수 있으며 subscribe 이전에 방출한 이벤트가 없다면 초기값을 전달한다.
보통 UIKit에서는 어떤 특정 값을 넣어줄 때 owner.name = random 이런식으로 구성해볼 수도 있지만 rx에서는 등위연산자라는 개념이 따로 존재하지 않기 때문에 onNext 이벤트를 통해서 값을 할당해줘야 한다.
이렇게 하게 되면 테이블뷰나 컬렉션 뷰와 같이 데이터가 변경되었을때도 reloadData를 하지 않아도 자동으로 뷰가 업데이트 되기 때문에 데이터가 많이 변경되야 하는 상황에서는 적절하게 사용해볼 수 있을 거 같다.
Observable의 특성상 complete가 발생하면 무조건 dispose가 실행되는데 이때 observable과 observer 사이의 연결은 dispose로 인해 끊겼기 때문에 구독이 끊긴 것이므로 이후의 이벤트는 실행될 수 없다. 그래서 구독하기 전의 이벤트 또한 받지 못한다!
// 상위뷰에 넘겨줄 신호
let buttonTap = PublishSubject<Void>()
...
moreCommunityLabel.rx.tap
.subscribe(onNext: { [weak self] in
print("셀 내부: 버튼 눌림 신호 발생")
self?.buttonTap.onNext(())
})
.disposed(by: disposeBag)
PublishSubject 같은 경우 초기값을 작성해주지 않아도 되기 때문에 개발할 때 보통 특정 버튼의 탭이 눌린다는 신호를 전달해야 하거나, 텍스트필드에 값이 들어왔다는 신호를 보낼때 주로 사용해볼 수 있었다.
subscribe 이후 시점부터 방출되는 이벤트를 처리할 수 있기 때문에 subscribe 이전에 방출된 이벤트들은 무시된다는 특성이 있다.
📍PublishSubject는 구독 이전의 값이 무시되는 이유?
: 버퍼관점에서 살펴보자! PublishSubject는 초기값을 넣지 않아도 되고, BehaviorSubject는 초기값을 반드시 할당해야 사용할 수 있었다. PublishSubject는 버퍼 크기가 0이기 때문에 구독 이전의 이벤트를 무시하고 신호만 전달해주는 역할을 한다.
반면 BehaviorSubject는 버퍼 크기가 1을 가지기 때문에 초기값을 반드시 할당해줌으로써 버퍼를 미리 채워두고 있다! BehaviorSubject는 현재 상태값을 다루는게 목적이기 때문에 구독 시점에 마지막 값을 알아두고 있어야 한다. 그래서 구독시 마지막 이벤트를 전달할 수 있는 것!!
4️⃣ Relay
Subject를 내부에서 wrapping해서 replay를 제공해주게 된 형태
Observable 타입에서 subscribe의 매개변수를 줄이기 위해 bind가 등장한 것처럼 subject에서도 똑같은 개념으로 여러개의 메서드를 제외하고 onNext 메서드만 남긴게 Relay이다.
Relay 또한 subject와 유사한 특성을 가지는 3가지가 존재한다.
- PublishRelay
- BehaviorRelay
- ReplayRelay
subject와 마찬가지로 초기값이 있으면 BehaviorRelay, 초기값이 없다면 PublishRelay을 사용한다.

subject와 Relay의 가장 큰 차이점으로 relay는 completed와 error에 대한 이벤트를 받지 못한다. 이 말은 즉 무한한 이벤트에 대한 처리방식과 일부 같으며 onNext에 대한 이벤트밖에 받지 못하기 때문에 disposed가 되기전까지는 subscribe가 해제되지 않는다.
그래서 직접 disposed가 호출되는 deinit 시점에 처리를 해줘야 한다!
Relay에서는 next 이벤트를 accept이라는 키워드로 사용한다.
private let date = BehaviorRelay(value: Date())
...
birthDayPicker.rx.date
.bind(with: self) { owner, date in
print(date)
owner.date.accept(date)
}
.disposed(by: disposeBag)
Rxswift에서 데이터를 취급하는 객체들이 어떤 이벤트를 방출할 수 있는지 아래 표를 통해 확인해볼 수 있다.

5️⃣ Drive
메인 스레드에서의 실행을 보장해준다.
bind는 하나의 구독을 공유하는 것이 아니라 각각 구독을 하는 방식이였다. 즉 스트림이 공유되고 있지 않다는 것을 의미하고 스트림이 공유될 수 있도록 drive를 통해서 수정할 수 있다.
네트워크 통신 같은 백그라운드에서 동작할 가능성이 있는 Observable의 데이터 흐름을 메인스레드에서 동작할 수 있도록 변경해주는 역할이 Drive이다!
output.errorMessage
.drive(with: self) { owner, message in
guard !message.isEmpty else { return }
owner.showAlert(title: "Error", message: message.description) {
print("[ClassItemDetailVC]: \(message.description)")
}
}
.disposed(by: disposeBag)
나의 경우 뷰모델에서 errorMessage를 받아오면 뷰에 표시되도록 drive를 사용해서 alert을 띄워주는 방식을 구현했다.
📍bind vs drive
: 가장 큰 차이점은 drive는 메인스레드에서 실행되는게 보장된다는 특성이 있지만 이외에도 스트림 관점에서 살펴보자.
bind는 subscribe를 편하게 사용하기 위한 wrapper이기 때문에 하나의 observable에 여러개의 bind를 걸게 되면 구독할 때 마다 새로운 스트림이 생기게 된다
반면 drive는 애초에 생성 목적이 여러개의 스트림을 공유하도록 만들어졌기 때문에 하나의 observable에 여러개의 drive를 걸어도 스트림이 한개만 생성된다
따라서 drive로 바꾸는 과정을 거치게 되면 네트워크 통신이나 버튼 액션을 한번만 실행시키게 되기 때문에 효율적으로 개선해볼 수 있게 될 거 같다.
그래서 subject는 bind와 주로 많이 사용되며 relay는 driver와 자주 사용되곤 한다!
참고
https://github.com/ReactiveX/RxSwift
'iOS > UIKit' 카테고리의 다른 글
| [UIKit] View Drawing Cycle (0) | 2026.01.26 |
|---|---|
| [UIKit] awakeFromNib vs prepareForReuse (1) | 2026.01.16 |
| [iOS/UIKit] Storyboard를 이용한 Auto Layout (2) | 2024.09.12 |
| [IOS/UIKit] UIImageView에 이미지 띄우기 (1) | 2024.01.09 |
| [IOS/UIKit] tableview 메서드들에 대하여 (1) | 2023.11.18 |
- Total
- Today
- Yesterday
- 코딩테스트
- prepareForReuse
- Fastlane
- internal Combine
- 프로그래머스
- 백준
- asyne-let
- CoreData
- Swift Format
- Task
- ObservableObject
- 스위프트
- rxswift
- group tasks
- UIKit
- foundation models
- awakeFromNib
- GCD
- SnapshotTest
- combine
- closure
- ios
- swiftUI
- Xcode
- AppGroup
- 클로저
- Swift Concurrency
- XCTest
- UITest
- SWIFT
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |