1. Subject
Subject는 Observable과 Observer의 역할을 동시에 수행할 수 있는 특수한 객체.
이를 통해 Subject는 구독을 통해 값을 방출하고, 외부에서 값을 받아 새로운 이벤트를 방출할 수 있음.
1-1. PublishSubject
PublishSubject 특징
- 초기에는 아무런 값도 방출되지 않기 때문에 구독 전에 발생한 값은 무시.
- 구독자가 구독한 시점 이후에 발생하는 next 이벤트만 전달.
- 구독자가 있는 동안 completed 또는 error 이벤트가 발생하면 모든 구독자에게 전달.
- 구독이 종료되거나 completed, error 이벤트가 발생하면 더 이상 이벤트가 방출되지 않음.
PublishSubject 예시
import RxSwift
func examplePublishSubject() {
let subject = PublishSubject<String>()
let disposeBag = DisposeBag()
// 구독자 없이 값 방출 (출력 없음)
subject.onNext("Is anyone listening?")
// 첫 번째 구독자
subject.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 첫 번째 구독자에게 값 전달
subject.onNext("1")
subject.onNext("2")
// 두 번째 구독자
subject.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
// 두 구독자에게 값 전달
subject.onNext("3")
}
Subscriber 1: 1
Subscriber 1: 2
Subscriber 1: 3
Subscriber 2: 3
PublishSubject 사용 사례
- 실시간 이벤트
- 채팅 메시지, 실시간 데이터 업데이트와 같이 구독 시점 이후 발생하는 시간에 민감한 데이터 처리에 유용.
- 알림 시스템
- 특정 이벤트가 발생했을 때 이를 구독자에게 전달하는 상황에 사용.
1-2. BehaviorSubject
BehaviorSubject 특징
- 항상 초기값을 가져야 함.
- 새로운 구독자는 가장 최근에 방출된 값을 즉시 받게 됨.
- 구독자가 없더라도 값이 방출될 때마다 저장되어, 나중에 구독하는 구독자에게 최신 값이 전달.
BehaviorSubject 예시
import RxSwift
func exampleBehaviorSubject() {
let subject = BehaviorSubject(value: "Initial Value")
let disposeBag = DisposeBag()
// 첫 번째 구독자 (즉시 초기값 전달)
subject.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 새로운 값 방출
subject.onNext("First Update")
// 두 번째 구독자 (최신 값 전달)
subject.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
// 추가 값 방출
subject.onNext("Second Update")
}
Subscriber 1: Initial Value
Subscriber 1: First Update
Subscriber 2: First Update
Subscriber 1: Second Update
Subscriber 2: Second Update
BehaviorSubject 사용 사례
- 상태 관리
- 네트워크 요청의 상태(로딩 중, 성공, 실패)를 나타내고, 새로운 구독자가 생길 때마다 최신 상태를 즉시 전달하는 데 유용.
- UI 초기화
- ViewController가 나타날 때 최신 데이터를 사용하여 UI를 초기화할 수 있음.
1-3. ReplaySubject
ReplaySubject 특징
- 생성 시점에 버퍼 크기를 지정하며, 지정된 크기만큼의 이벤트를 저장.
- 새로운 구독자가 구독할 때 저장된 이벤트를 모두 전달.
- 메모리를 더 많이 사용하므로 적절한 버퍼 크기 설정 필요.
ReplaySubject 예시
import RxSwift
func exampleReplaySubject() {
let subject = ReplaySubject<String>.create(bufferSize: 2)
let disposeBag = DisposeBag()
// 두 개의 값 방출 (버퍼에 저장)
subject.onNext("1")
subject.onNext("2")
// 세 번째 값 방출 (버퍼에 저장, 가장 오래된 값 삭제)
subject.onNext("3")
// 첫 번째 구독자: 가장 최근 두 개의 값 받음
subject.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 네 번째 값 방출
subject.onNext("4")
// 두 번째 구독자: 가장 최근 두 개의 값 받음
subject.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
}
Subscriber 1: 2
Subscriber 1: 3
Subscriber 1: 4
Subscriber 2: 3
Subscriber 2: 4
ReplaySubject 사용 사례
- 캐시된 데이터 전달
- 여러 구독자에게 과거 데이터를 캐싱하여 전달할 때 유용.
- 최근 검색 기록
- 사용자의 최근 검색어를 저장하고, 새로운 구독자가 구독할 때 과거 검색어를 전달 가능.
1-4. AsyncSubject
AsyncSubject 특징
- 시퀀스 완료 시 마지막 next 이벤트만 전달.
- completed 이벤트가 발생해야 구독자에게 값이 전달.
- error 이벤트가 발생하면 값을 전달하지 않음.
AsyncSubject 예시
import RxSwift
func exampleAsyncSubject() {
let subject = AsyncSubject<String>()
let disposeBag = DisposeBag()
// 첫 번째 구독자
subject.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 값 방출 (아직 전달되지 않음)
subject.onNext("1")
subject.onNext("2")
// 두 번째 구독자
subject.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
// 시퀀스 완료
subject.onCompleted()
}
Subscriber 1: 2
Subscriber 2: 2
AsyncSubject 사용 사례
- 단일 결과 처리
- 비동기 작업이 완료되었을 때 최종 결과만을 필요로 하는 경우에 유용.
- 예를 들어, 파일 다운로드가 완료되었을 때 다운로드 된 파일의 경로를 전달하는 데 사용할 수 있음.
1-5. PublishSubject vs. BehaviorSubject vs. ReplaySubject vs. AsyncSubject
PublishSubject | BehaviorSubject | ReplaySubject | AsyncSubject | |
초기값 | 없음 | 필수 (초기값을 설정해야 함) | 없음 | 없음 |
구독 시점의 값 전달 | 없음 (구독 이후 발생하는 값만 전달) | 최신 값 (구독 시점의 가장 최근 값 전달) | 저장된 과거 값 (버퍼 크기만큼 과거 값 전달) | 시퀀스가 완료될 때 가장 마지막 값 전달 |
이벤트 버퍼링 | 버퍼링 없음 | 최근 하나의 값만 유지 | 버퍼 크기만큼 이벤트 저장 | 시퀀스가 완료될 때 마지막 값만 전달 |
에러 이벤트 | 에러 발생 시 모든 구독자에게 전달, 새로운 구독자도 에러 전달 | 에러 발생 시 모든 구독자에게 전달, 새로운 구독자도 에러 전달 | 에러 발생 시 모든 구독자에게 전달, 새로운 구독자도 에러 전달 | 에러 발생 시 마지막 값이 아닌 에러만 전달 |
완료 이벤트 | 완료 시 모든 구독자에게 전달, 새로운 구독자도 완료 전달 | 완료 시 모든 구독자에게 전달, 새로운 구독자도 완료 전달 | 완료 시 모든 구독자에게 전달, 새로운 구독자도 완료 전달 | 시퀀스 완료 시 마지막 값 전달 |
주요 사용 사례 | 버튼 클릭, 알림 등 구독 시점 이후 발생하는 이벤트 처리 | 상태 관리, 최신 상태를 유지하고 즉시 전달해야 하는 경우 | 검색 기록, 캐시된 데이터 등 과거 이벤트 재생 필요 시 | 비동기 작업 완료 후 최종 결과를 전달해야 하는 경우 |
선택 가이드
- PublishSubject를 선택할 때
- 구독 시점 이전에 발생한 이벤트는 중요하지 않고, 구독 이후 발생하는 이벤트만 처리하고자 하는 경우.
- 예: 버튼 클릭 이벤트 처리, 실시간 알림 등.
- BehaviorSubject를 선택할 때
- 상태 관리가 필요하고, 구독자가 구독할 때마다 최신 상태를 즉시 전달해야 하는 경우.
- 예: 사용자 프로필, 로그인 상태 등 최신 상태를 유지하는 시나리오.
- ReplaySubject를 선택할 때
- 구독자가 과거에 발생한 이벤트들을 모두 받아야 하는 경우.
- 예: 최근 검색 기록, 캐시된 데이터 전달 등.
- AsyncSubject를 선택할 때
- 시퀀스가 완료된 후 마지막 값만 필요한 경우.
- 비동기 작업의 최종 결과만을 전달해야 하는 경우.
- 예: 파일 다운로드 완료 후 파일 경로 전달, 작업 완료 후 결과 처리.
1-6. Subject 정리
RxSwift의 Subject는 다양한 비동기 시나리오에서 데이터를 방출하고 수신할 수 있는 중요한 역할을 담당.
각 Subject는 고유한 특성을 가지고 있으며, 다음과 같은 경우에 사용할 수 있음.
- PublishSubject: 구독 시점 이후에 발생하는 이벤트만 필요한 경우.
- BehaviorSubject: 구독자가 구독할 때 최신 상태를 즉시 전달해야 하는 경우.
- ReplaySubject: 과거 이벤트를 캐싱하여 새로운 구독자에게 전달해야 하는 경우.
- AsyncSubject: 시퀀스가 완료된 후 마지막 값만 필요할 때.
2. Relay
Relay는 내부적으로 Subject를 래핑한 것으로, Subject의 기능을 더 안전하게 사용할 수 있음.
completed나 error 이벤트를 방출하지 않기 때문에, UI 컴포넌트와 같이 지속적인 이벤트를 처리해야 하는 상황에 주로 사용.
2-1. PublishRelay
PublishRelay 특징
- 초기값을 필요로 하지 않음.
- 구독자가 구독한 시점 이후에 발생하는 next 이벤트만 전달.
- completed나 error 이벤트를 방출하지 않음.
PublishRelay 예시
import RxSwift
import RxRelay
func examplePublishRelay() {
let relay = PublishRelay<String>()
let disposeBag = DisposeBag()
// 구독자 없이 값 방출 (출력되지 않음)
relay.accept("Is anyone listening?")
// 첫 번째 구독자
relay.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 값 방출
relay.accept("Hello")
relay.accept("World")
// 두 번째 구독자
relay.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
// 추가 값 방출
relay.accept("RxSwift")
relay.accept("Relay")
}
Subscriber 1: Hello
Subscriber 1: World
Subscriber 1: RxSwift
Subscriber 2: RxSwift
Subscriber 1: Relay
Subscriber 2: Relay
PublishRelay 사용 사례
- 버튼 클릭 이벤트
- UI에서 버튼 클릭과 같은 이벤트 처리에 사용.
- 실시간 데이터 스트림
- 채팅 메시지나 실시간 알림과 같이 구독 시점 이후의 데이터 스트림을 처리할 때 유용.
2-2. BehaviorRelay
BehaviorSubject를 래핑한 Relay로, 항상 현재값을 유지하며, 새로운 구독자에게 구독 시점의 현재값을 즉시 전달합니다.
BehaviorRelay는 초기값을 필요로 하며, 구독자가 생길 때마다 최신 값을 전달합니다.
BehaviorRelay 특징
- 초기값 필수.
- 항상 현재값을 유지하며, 새로운 구독자에게 즉시 전달.
- completed나 error 이벤트를 방출하지 않음.
- value 프로퍼티를 통해 현재값을 직접 접근 가능.
BehaviorRelay 예시
import RxSwift
import RxRelay
func exampleBehaviorRelay() {
let relay = BehaviorRelay(value: "Initial Value")
let disposeBag = DisposeBag()
// 첫 번째 구독자 (즉시 초기값을 받음)
relay.subscribe(onNext: { value in
print("Subscriber 1: \\\\(value)")
}).disposed(by: disposeBag)
// 값 방출
relay.accept("First Update")
// 두 번째 구독자 (현재값인 "First Update"를 즉시 받음)
relay.subscribe(onNext: { value in
print("Subscriber 2: \\\\(value)")
}).disposed(by: disposeBag)
// 추가 값 방출
relay.accept("Second Update")
// 현재값 직접 접근
print("Current Value: \\\\(relay.value)")
}
Subscriber 1: Initial Value
Subscriber 1: First Update
Subscriber 2: First Update
Subscriber 1: Second Update
Subscriber 2: Second Update
Current Value: Second Update
BehaviorRelay 사용 사례
- 상태 관리
- 앱의 상태(예: 로그인 상태, 사용자 프로필 데이터)를 관리하고, 상태가 변경될 때마다 UI를 업데이트할 때 유용.
- 폼 데이터 관리
- 사용자 입력 폼의 현재 값을 유지하고, 필요할 때 접근할 수 있도록 할 때 사용.
- 설정 데이터
- 앱의 설정 값이나 환경 설정 데이터를 관리하고, 변경 시 UI에 즉시 반영할 때 유용.
2-3. PublishRelay vs. BehaviorRelay
PublishRelay | BehaviorRelay | |
초기값 | 없음 | 필수 |
현재값 유지 | 아니오 | 예 |
새로운 구독자에게 전달되는 값 | 구독 시점 이후의 next 이벤트만 | 구독 시점의 현재값 즉시 전달 |
value 프로퍼티 접근 가능 | 아니오 | 예 |
사용 사례 | 이벤트 스트림, 버튼 클릭 등 | 상태 관리, 현재 값 필요 시 접근 |
선택 가이드
- PublishRelay를 선택할 때
- 단순 이벤트 스트림이 필요한 경우.
- 이벤트를 구독자에게 전달하고, 상태를 유지할 필요가 없는 경우
- BehaviorRelay를 선택할 때
- 상태 관리가 필요한 경우.
- 현재 상태를 유지하고, 새로운 구독자에게 최신 상태를 즉시 전달해야 하는 경우
2-4. Relay 추가 사용 예시
PublishRelay를 이용한 버튼 클릭 이벤트 처리
import UIKit
import RxSwift
import RxCocoa
import RxRelay
class ViewController: UIViewController {
let disposeBag = DisposeBag()
let buttonClickRelay = PublishRelay<Void>()
override func viewDidLoad() {
super.viewDidLoad()
// 버튼 생성 및 설정
let button = UIButton(type: .system)
button.setTitle("Click Me", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// 버튼 레이아웃 설정
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
// 버튼 클릭 시 Relay에 이벤트 전달
button.rx.tap
.bind(to: buttonClickRelay)
.disposed(by: disposeBag)
// Relay 구독하여 클릭 이벤트 처리
buttonClickRelay.subscribe(onNext: {
print("Button was clicked!")
}).disposed(by: disposeBag)
}
}
- 버튼 클릭 이벤트를 PublishRelay를 통해 처리.
- 버튼이 클릭될 때마다 buttonClickRelay에 Void 이벤트가 전달되고, 이를 구독하여 콘솔에 메시지를 출력.
- PublishRelay는 이벤트 스트림을 단순하게 전달하기에 적합.
BehaviorRelay를 이용한 로그인 상태 관리
import RxSwift
import RxRelay
enum LoginState {
case loggedIn(User)
case loggedOut
}
struct User {
let username: String
}
class SessionManager {
static let shared = SessionManager()
// BehaviorRelay를 이용하여 현재 로그인 상태를 관리
private(set) var loginStateRelay = BehaviorRelay<LoginState>(value: .loggedOut)
private init() {}
func logIn(username: String) {
let user = User(username: username)
loginStateRelay.accept(.loggedIn(user))
}
func logOut() {
loginStateRelay.accept(.loggedOut)
}
}
import RxSwift
let disposeBag = DisposeBag()
// 로그인 상태 구독
SessionManager.shared.loginStateRelay.subscribe(onNext: { state in
switch state {
case .loggedIn(let user):
print("\\\\(user.username) has logged in.")
case .loggedOut:
print("User has logged out.")
}
}).disposed(by: disposeBag)
// 로그인 및 로그아웃 이벤트 발생
SessionManager.shared.logIn(username: "JohnDoe")
// 출력: JohnDoe has logged in.
SessionManager.shared.logOut()
// 출력: User has logged out.
- BehaviorRelay를 사용하여 로그인 상태를 관리.
- loginStateRelay는 현재 로그인 상태를 유지하며, 새로운 구독자가 생기면 최신 상태를 즉시 전달.
- 로그인 및 로그아웃 시점에 따라 BehaviorRelay에 새로운 상태를 accept하여 구독자에게 전달.
장점
- 현재 상태를 항상 유지하므로, 새로운 구독자가 생겨도 최신 상태를 즉시 확인 가능.
- 상태 관리가 명확하고 일관되게 이루어 짐.
2-5. Relay 정리
Relay는 RxSwift에서 안전하고 예측 가능한 이벤트 처리와 상태 관리를 위해 강력한 도구.
Relay를 사용함으로써 Subject의 복잡성과 위험성을 줄이고, 더 깔끔한 코드 구조를 유지할 수 있음.
- PublishRelay: 단순한 이벤트 스트림 처리에 적합하며, 완료나 에러 이벤트가 필요 없는 경우 사용.
- BehaviorRelay: 상태 관리가 필요한 경우에 유용하며, 현재 상태를 유지하고 새로운 구독자에게 최신 상태를 즉시 전달 가능.
'RxSwift' 카테고리의 다른 글
[RxSwift] RxSwift로 키보드가 화면을 가릴 때 해결하기! (0) | 2024.06.27 |
---|---|
[RxSwift] Single (0) | 2024.06.03 |
[RxSwift] Subscribe vs. Bind vs. Drive (0) | 2024.05.31 |
[RxSwift] Driver (0) | 2024.05.31 |
[RxSwift] Observable, Observer, Subscribe, Dispose (0) | 2024.05.23 |