[RxSwift #4] Subject 와 Relay

2021. 9. 22. 10:38ReactiveX

Rx를 이용해서 코딩을 하다보면 Subject와 Relay를 사용해야 할 때가 생긴다. 예를 들면 내가 다른 데이터를 관찰해서 내 정보를 업데이트해야 하고, 또 누군가가 나를 관찰해야 하는 입장이 될 때가 있다. 이럴 때 어떻게 해결을 해야 할지 고민하다가 찾은 것이 Subject와 Relay였다. Rx 관련 개념들에 대해 대충 찾아봤을 때 지나가다가 본 것들이었는데, 이것을 써야만 하는 상황이 생기다니...!

 

물론 아주 흔한 상황인 것 같다(ㅋㅋㅋㅋㅋ)

 

하지만 PublishSubject, PublishRelay, BehaviorSubject, BehaviorRelay..... 등등등 정말 많은 것들이 있었다. 어떤 것은 초깃값을 주지 않아도 객체를 생성할 수 있었고, 어떤 것은 아니었다. 이것 뿐만 아니라 이 많은 것들 사이에는 차이점이 분명 있을 거고 공통된 단어가 가지는 개념이 있을 것이라고 생각했다. 그래서 이들간의 차이점을 공부해보았다.

 

단어들을 자세히 살펴보면 단어 끝에 붙는 것들로는 Subject, Relay가 있다. 그리고 단어 앞에 붙는 것들로는 Publish, Behavior, Replay가 있다.

 

일단 Subject와 Relay에 대해 알아본 후 각각의 세부적인 종류를 살펴보자!

 

1. Subject

공식 문서에는 다음과 같이 정리되어있다.

A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable. 

 -> Subject는 observer와 Observable 모두로 작동할 수 있는 ReactiveX 구현에서 사용할 수 있는 일종의 브릿지 또는 프록시이다. 

 

즉 Subject는 Observable이면서 observer이다. observer이기 때문에 다른 Observable을 구독할 수 있으며, Observable이기 때문에 자신을 구독한 observer들에게 이벤트를 방출할 수 있다. 

 

Hot / Cold Observable

이전 글에서 Hot Observable과 Cold Observable에 대해 언급한 적이 있는데, Subject를 사용하면 Hot Observable을 생성할 수 있다.

 

전에 봤듯이 그냥 Observable은 subscribe가 호출될 때 생성된다. 즉 subscribe가 되기 전에는 옵저버블이 정의만 되어있고 생성되지 않았다. 이는 subscribe후에만 이벤트를 방출하는 Cold Observable이다.

 

하지만 Subject는 subscribe 전에도 생성이 가능하다. 누군가 자신을 구독하기 전에도 이벤트를 방출할 수 있다. 그래서 이를 Hot Observable이라고 한다. 

 

 

Subject의 종류

Subject에는 AsyncSubject, BehaviorSubject, PublishSubject, ReplaySubject 이렇게 네 가지 종류가 있다. 이들은 다른 Observable을 구독한다는 점에서는 동일하지만, 자신을 구독한 observer에게 값을 방출하는 방식에서 차이가 있다.

 

그럼 하나씩 살펴보도록 하자.

 

1) AsyncSubject

AsyncSubject를 구독하면, 구독한 시점과 상관 없이 complete되기 직전 값을 방출받게 된다.

completed

하지만 error로 종료되면 구독한 시점과 상관 없이 아무 값도 방출 받지 못한다. 

error

 

2) BehaviorSubject

아래 그림을 보면 BehaviorSubject 옆에 핑그색 동그라미가 보인다. 이는 생성 시 초깃값을 넣어줘야 한다는 뜻이다.

 

BehaviorSubject는 observer가 구독한 시점을 기준으로 구독 전 가장 최신의 값을 방출하기 때문이다. 아직 아무 값도 방출하지 않은 시점에서는 초깃값으로 설정한 값을 방출한다. 그리고 구독한 이후에 발생하는 값들도 계속 방출해준다.

completed

에러로 종료된 시점 이후에 구독하면 다른 값은 방출받지 못한다.

error

BehaviorSubject는 가장 최신의 값을 얻는 것이 중요할 때 사용하기 좋다. 최신 정보를 업데이트 할 때 좋겠다.

 

3) PublishSubject

PublishSubject를 구독한 observer는 구독한 시점 이후부터 발생하는 모든 값들을 방출받는다. 따라서 이것은 생성할 때 초깃값을 설정해줄 필요가 없다.

completed

에러로 종료된 경우에도 같은 방식으로 동작한다. 에러가 발생한 후에 구독하면 아무 값도 방출받지 못한다.

error

 

4) ReplaySubject

ReplaySubject는 특정 크기 만큼 값들을 저장해두고 방출한다. 즉 observer는 ReplaySubject를 구독하기 이전의 값들까지 받을 수 있다. 구독한 후에는 값이 새로 발생할 때마다 방출받을 수 있다. 따라서 구독한 시점보다 전에 있던 값들을 알고 싶을 때 유용하다. 최신 정보들을 확인하고 싶을 때 사용할 수 있을 것이다.

completed

 

2. Relay

이제까지 Subject들에 관해 알아봤다. 그럼 Relay는 무엇일까?

 

Subject는 RxSwift를 import하면 사용할 수 있었는데, Relay는 RxCocoa 안에 있다. Relay는 Subject의 Wrapper 클래스로, PublishRealy와 BehaviorRelay가 있다.

 

Subject와의 가장 큰 차이점은 Relay는 complete와 error를 발생시킬 수 없다는 것이다. Dispose되기 전까지는 계속 작동한다.

따라서 Relay는 UI 작업을 하기에 적합하다.

 

1) BehaviorRelay

먼저 BehaviorRelay를 구현한 코드를 살펴보자.

public final class BehaviorRelay<Element>: ObservableType {
    private let _subject: BehaviorSubject<Element>

    /// Accepts `event` and emits it to subscribers
    public func accept(_ event: Element) {
        self._subject.onNext(event)
    }

    /// Current value of behavior subject
    public var value: Element {
        // this try! is ok because subject can't error out or be disposed
        return try! self._subject.value()
    }

    /// Initializes behavior relay with initial value.
    public init(value: Element) {
        self._subject = BehaviorSubject(value: value)
    }

    /// Subscribes observer
    public func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        return self._subject.subscribe(observer)
    }

    /// - returns: Canonical interface for push style sequence
    public func asObservable() -> Observable<Element> {
        return self._subject.asObservable()
    }
}

BehaviorSubject를 감싸고 있는 Wrapper 클래스라는 것을 알 수 있다.

 

내부에 accept라는 함수가 보이는데, Relay는 이벤트를 방출할 때 onNext 대신에 accept를 사용한다.

BehaviorSubject와 같이 BehaviorRelay도 생성시 초깃값이 필요하다는 사실도 알 수 있다.

 

 

2) PublishRelay

PublishRelay를 구현한 코드는 다음과 같다.

import RxSwift

/// PublishRelay is a wrapper for `PublishSubject`.
///
/// Unlike `PublishSubject` it can't terminate with error or completed.
public final class PublishRelay<Element>: ObservableType {
    private let _subject: PublishSubject<Element>
    
    // Accepts `event` and emits it to subscribers
    public func accept(_ event: Element) {
        self._subject.onNext(event)
    }
    
    /// Initializes with internal empty subject.
    public init() {
        self._subject = PublishSubject()
    }

    /// Subscribes observer
    public func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        return self._subject.subscribe(observer)
    }
    
    /// - returns: Canonical interface for push style sequence
    public func asObservable() -> Observable<Element> {
        return self._subject.asObservable()
    }
}

PublishRelay는 PublishSubject를 감싼 Wrapper 클래스이며, error나 complete를 발생시킬 수 없다.

PublishRelay도 PublishSubject처럼 생성 시 초깃값을 설정해 줄 필요가 없다. 

 

 

이렇게 Subject와 Relay에 관해 알아보았다.

다음 글은 Relay를 구독하는 방법과 Driver에 대해 알아보도록 하자.

'ReactiveX' 카테고리의 다른 글

[RxSwift #6] Traits(1) - RxSwift Traits  (0) 2022.01.07
[RxSwift #5] Driver란 무엇일까?  (0) 2022.01.01
[RxSwift #3] Scheduler  (0) 2021.09.20
[RxSwift #2] Observable에 대해  (0) 2021.09.19
[RxSwift #1] 자주 쓰이는 용어와 개념  (0) 2021.09.17