Search

Subject와 Relay

Subjects

Subjects는 Observable이자 Observer 역할을 한다. 따라서 Subject는 데이터를 방출(emit)하고 다른 Observable에서 데이터 이벤트를 받아들일 수 있다. RxSwift에서 제공하는 주요 Subject는 다음과 같다.
1.
Publish Subject
2.
Behavior Subject
3.
Replay Subject
4.
Async Subject

Relays

Relays는 RxCocoa에 포함되어 있으며 기본적으로 Subjects와 비슷하게 동작하지만 onCompleted나 onError 이벤트를 방출하지 않는다.

Publish Subject

Publish Subject는 새로운 구독자가 기존에 발행된 이벤트를 받을 수 없고 구독한 시점 이후의 이벤트만 받을 수 있다.
import RxSwift // MARK: - PublishSubject // 구독이 이루어진 시점 이후의 새로운 이벤트만 구독자에게 전달함. // 초기값을 가지지 않음. let disposeBag = DisposeBag() let publishSubject = PublishSubject<String>() // "Hello" 이벤트를 방출하지만 현재 구독자가 없기 때문에 이 이벤트는 아무도 받지 못함. publishSubject.onNext("Hello") // 첫 번째 구독자 추가 publishSubject.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) publishSubject.onNext("World") // 두 번째 구독자 추가 publishSubject.subscribe(onNext: { print("Subscriber 2: \($0)") }).disposed(by: disposeBag) publishSubject.onNext("RxSwift")
Swift
복사
출력 결과
Subscriber 1: World Subscriber 1: RxSwift Subscriber 2: RxSwift
Swift
복사
Publish Subject는 구독 시점 이후의 이벤트만 구독자에게 전달한다. 따라서 “Hello” 이벤트는 구독자가 없어서 아무도 받지 못했다. “World” 이벤트는 첫 번째 구독자가 수신했고 두 번째 구독자는 “World” 이벤트 이후에 구독했기 때문에 “RxSwift” 이벤트만 수신했으며 “RxSwift” 이벤트는 모든 구독자가 수신했다.
장점
구독이 이루어진 시점 이후의 새로운 이벤트만 구독자에게 전달함. 따라서 새로운 구독자가 생기면 그 시점 이후의 이벤트만 받을 수 있어 구독 관리에 용이함.
초기값을 가지지 않으므로 구독자가 생길 때마다 이전 값을 전달하지 않음. 이는 초기값이 필요없을 때 유용함.
초기값을 가지지 않기 때문에 메모리 사용과 성능 면에서 최적화된 선택이 될 수 있음. 초기값을 저장하고 전달하는 과정이 생략되므로 메모리 효율성이 높아짐.
단점
구독자가 없을 때 발행된 이벤트는 모두 손실됨. 즉, 구독자가 늦게 추가되는 경우 필요한 이벤트를 놓칠 수 있음.
초기값을 가지지 않기 때문에 새로운 구독자는 구독 시점 이전의 상태나 값을 받을 수 없음. 초기 상태가 필요한 경우에는 문제가 될 수 있음.
여러 스레드에서 PublishSubject에 이벤트를 전달할 때 동시성 문제가 발생할 수 있음.

Behavior Subject

Behavior Subject는 새로운 구독자에게 가장 최근에 발행된 값을 전달하고 그 이후의 이벤트도 전달한다.
import RxSwift // MARK: - BehaviorSubject let disposeBag = DisposeBag() let behaviorSubject = BehaviorSubject<String>(value: "Initial Value") // 구독 즉시 "Initial Value" 출력 behaviorSubject.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) // onNext 이벤트가 발생하면 해당 이벤트 전달 // 구독 중인 모든 구독자에게 "Hello" 전달 behaviorSubject.onNext("Hello") // 두 번째 구독자는 구독 즉시 가장 최근에 방출된 "Hello" 이벤트를 전달받음 behaviorSubject.subscribe(onNext: { print("Subscriber 2: \($0)") }).disposed(by: disposeBag) // 구독 중인 모든 구독자에게 "World" 전달 behaviorSubject.onNext("World")
Swift
복사
출력 결과
Subscriber 1: Initial Value Subscriber 1: Hello Subscriber 2: Hello Subscriber 1: World Subscriber 2: World
Swift
복사
장점
초기값을 설정할 수 있어서 구독자가 구독하는 즉시 해당 값을 받을 수 있음. 특히 초기 상태나 기본 값을 제공할 때 유용함.
항상 가장 최근에 발행된 값을 유지함. 새로운 구독자는 구독 시점에 이 최신값을 즉시 받을 수 있음.
구독 중인 모든 구독자에게 onNext 이벤트를 전달함. 실시간으로 업데이트되는 데이터를 다룰 때 유용함.
단점
초기값을 반드시 설정해야하므로 초기값을 설정하기 어려운 상황에서는 불편할 수 있음.
항상 최신값을 유지하므로 구독자는 쉽게 최신 상태를 볼 수 있지만 일부 구독자는 원하지 않는 상태일 수도 있음.
단 하나의 최신값만 유지함. 따라서 과거의 이벤트들을 모두 유지해야 하는 경우에는 적합하지 않음.

Replay Subject

Replay Subject는 버퍼 크기를 설정할 수 있으며 새로운 구독자에게 버퍼에 있는 이벤트를 모두 전달한다.
import RxSwift // MARK: - ReplaySubject // 버퍼 크기를 설정하여 새 구독자가 구독할 때 설정된 크기만큼의 최근 이벤트를 전달함. let disposeBag = DisposeBag() // 버퍼 크기를 2로 설정 // 항상 마지막 두 개의 이벤트를 버퍼에 저장 let replaySubject = ReplaySubject<String>.create(bufferSize: 2) // 버퍼의 크기가 2이기 때문에 "1"은 버퍼에서 제거 replaySubject.onNext("1") // "2"와 "3"만 버퍼에 저장 replaySubject.onNext("2") replaySubject.onNext("3") // 'Subscriber 1'이 ReplaySubject를 구독 // 구독 시점에 버퍼에 있는 이벤트("2", "3")가 'Subscriber 1'에게 즉시 전달 replaySubject.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) // 이후 방출되는 이벤트도 전달 // 하지만 버퍼 크기가 2이기 때문에 가장 오래된 값인 "2"가 제거되고 "3"과 "4"가 저장 replaySubject.onNext("4") // 'Subscriber 2'가 구독한 시점에 버퍼에 있는 이벤트를 즉시 전달 // 따라서 "3"과 "4"가 출력 replaySubject.subscribe(onNext: { print("Subscriber 2: \($0)") }).disposed(by: disposeBag)
Swift
복사
출력 결과
Subscriber 1: 2 Subscriber 1: 3 Subscriber 1: 4 Subscriber 2: 3 Subscriber 2: 4
Swift
복사
장점
버퍼 크기만큼의 이벤트를 저장하고 새로운 구독자에게 이를 전달함. 이는 구독자가 나중에 구독하더라도 중요한 이벤트를 놓치지 않도록 보장함. 예를 들어, ‘Subscriber 1’과 ‘Subscriber 2’ 모두 중요한 마지막 두 개의 이벤트(”3”, “4”)를 받을 수 있음.
최신 상태를 유지하며 구독자가 언제든지 현재 상태를 확인할 수 있도록 함. 이는 상태 유지 및 전달이 중요한 경우에 유용함.
구독자는 구독 시점부터 버퍼 크기만큼의 이벤트를 즉시 받기 때문에 필요한 초기 데이터나 상태를 놓치지 않음. 이는 데이터 스트림이 시작된 이후에 구독자가 추가되는 경우에 특히 유용함.
단점
설정된 버퍼 크기만큼의 이벤트를 메모리에 저장함. 버퍼 크기가 클수록 더 많은 메모리를 사용하게 되며 리소스가 제한된 환경에서 문제를 일으킬 수 있음. 예를 들어, 버퍼 크기를 1,000으로 설정하면 1,000개의 이벤트를 메모리에 저장해야 됨.
버퍼에 저장된 이벤트가 많아질수록 새로운 구독자가 구독할 때 이벤트를 전달하는 데 시간이 오래 걸릴 수 있음.
버퍼 크기를 잘못 설정하면 불필요한 이벤트가 버퍼에 저장될 수 있고 필요한 이벤트가 버퍼에서 제거될 수 있음. 이는 데이터 손실 또는 블필요한 데이터 전송 문제를 일으킬 수 있음.

Async Subject

Async Subject는 완료되었을 때 마지막으로 발행된 값을 구독자에게 전달한다.
import RxSwift // MARK: - AsyncSubject // AsyncSubject는 일반적인 Subject와는 달리, // Observable이 완료되었을 때 마지막으로 발행된 값을 구독자에게 전달함. let disposeBag = DisposeBag() let asyncSubject = AsyncSubject<String>() // 구독자 1은 이후에 발행된 값과 // 완료 이벤트가 발생할 때의 마지막 값을 받을 준비가 됨 asyncSubject.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) // "1"과 "2" 값을 발행 // 하지만 이 시점에서는 완료 이벤트가 발생할 때까지 어떤 구독자에게도 값이 전달되지 않음 asyncSubject.onNext("1") asyncSubject.onNext("2") // 구독자 2도 마지막 값을 받을 준비가 됨 asyncSubject.subscribe(onNext: { print("Subscriber 2: \($0)") }).disposed(by: disposeBag) // 이 값은 현재까지 발행된 마지막 값이지만 아직 두 구독자에게 전달되지 않음 asyncSubject.onNext("3") // 완료 이벤트 발행 // 이 시점에서 마지막으로 발행된 "3"을 모든 구독자에게 전달됨 asyncSubject.onCompleted()
Swift
복사
출력 결과
Subscriber 1: 3 Subscriber 2: 3
Swift
복사
장점
마지막 값만 전달하기 때문에 메모리 관리가 효율적
완료될 때 마지막 값을 전달하므로 특정 작업의 최종 결과를 필요로 하는 경우 적합
단점
완료 이벤트가 발생하지 않으면 값이 전달되지 않음
완료되기 전에 구독자들이 값을 받을 수 없기 때문에 실시간 데이터 업데이트로는 부적합

Behavior Relay

Behavior Relay는 Behavior Subject와 유사하지만 onCompleted나 onError 이벤트를 발행하지 않는다.
import RxCocoa import RxSwift // MARK: - BehaviorRelay // 주로 상태를 관리하거나 UI와 데이터 사이의 연결고리 역할을 하는데 유용함. let disposeBag = DisposeBag() let behaviorRelay = BehaviorRelay<String>(value: "Initial Value") // 현재 구독자가 없기 때문에 출력은 없음 // onNext와 유사하지만 onCompleted나 onError 이벤트를 발행하지 않음 behaviorRelay.accept("Hello") // 구독하여 값이 변경될 때마다 실행되는 클로저 설정 behaviorRelay.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) // 현재 구독자가 있으므로 출력 behaviorRelay.accept("World")
Swift
복사
출력 결과
Subscriber 1: Hello Subscriber 1: World
Swift
복사
장점
간단한 상태 관리에 유용함. 초기값과 최신 값을 항상 유지할 수 있음.
주로 UI와 데이터 사이의 바인딩에 사용되며 UI 컴포넌트의 상태를 쉽게 관리할 수 있음
단점
특정 시점의 상태를 저장해야 하는 경우에는 적합하지 않을 수 있음
과거 여러 이벤트를 저장하지 않기 때문에 새로운 구독자가 이전 여러 이벤트를 받을 수 없음
에러 처리가 필요한 경우 적합하지 않음

Publish Relay

Publish Replay는 Publish Subject와 유사하지만 마찬가지로 onCompleted나 onError 이벤트를 발행하지 않는다.
import RxCocoa import RxSwift // MARK: - PublishRelay let disposeBag = DisposeBag() // 초기값을 가지지 않음 let publishRelay = PublishRelay<String>() // 구독자가 없기 때문에 출력되지 않음 publishRelay.accept("Hello") publishRelay.subscribe(onNext: { print("Subscriber 1: \($0)") }).disposed(by: disposeBag) publishRelay.accept("World")
Swift
복사
출력 결과
Subscriber 1: World
Swift
복사
장점
완료 및 에러 처리를 할 필요가 없어 코드가 단순함. 따라서 스트림이 중단되는 것을 방지
주로 사용자의 입력 이벤트나 UI 액션과 같은 단발성 이벤트 스트림에 적합
단점
구독자가 존재하지 않을 때 발행된 이벤트는 전달되지 않기 때문에 특정 시점의 이벤트를 놓칠 수 있음