본문 바로가기

iOS

[iOS] [Swift] RxSwift Subject & Relay & Driver

Subjects & Relays

 

이번 시간에는 SubjectRelay 에 대하여 알아보도록 하겠습니다.

 

앞서 저희는 이벤트를 방출하는 Observable 과 이를 구독하여 이벤트를 처리하는 Observer에 대하여 알아보았습니다.

 

Observable과 Observer는 역할이 확실하게 구분되어져 있습니다.

 

Observable은 Observer와 달리 다른 Observable을 구독하지 못합니다.

 

마찬가지로 Observer는 다른 Observer에게 본인이 받은 이벤트를 전달하지 못하죠.

 

둘의 역할이 명확히 구분되어 있는 만큼 이로 인한 유연성에 대한 한계도 존재합니다.

 

이러한 부분에 대한 편의성을 제공하고자 나온것이 Subject 입니다.

 

Subject는 Observable 이면서 Observer 역할을 합니다.

 

즉, 이벤트를 방출할수도 있고 이와 동시에 구독 또한 가능합니다.

 

따라서 다른 Observable로 부터 이벤트를 받아서 이를 Observer에게 전달할 수 있는 징검다리 역할 또한 할 수 있게 되죠.

 

RxSwift에서는 4가지 종류의 Subject 를 제공합니다.

이 4가지 Subject를 순차적으로 알아보도록 하겠습니다.

 

1. PublishSubject


가장 기본적인 형태의 Subject 인 PublishSubject 입니다.

 

image

 

Observable과 마찬가지로 제네릭 타입을 채택하고 있어 보다 타입에 대하여 유연하게 작동합니다.

 

let intSubject = PublishSubject<Int>() // Int type subject
let stringSubject = PublishSubject<String>() // String type subject

 

PublishSubject를 생성할 때에는 생성자에 별 다른 파라미터를 전달하지 않습니다.

 

즉, 비어있는 상태로 Subject가 생성되어 생성되는 시점에는 내부에 아무런 이벤트가 저장되어 있지 않습니다.

 

따라서 생성 직후 Observer가 이를 구독하면 아무런 이벤트도 전달되지 않습니다.

 

우리는 앞서 SubjectObservable 이면서 Observer 인 역할을 한다고 배웠습니다.

 

let intSubject = PublishSubject<Int>()

intSubject.onNext(3)

 

위와 같이 Observable 처럼 이벤트를 만들어 낼 수도 있구요!

 

let intSubject = PublishSubject<Int>()
let disposeBag = DisposeBag()

intSubject.onNext(3)

intSubject.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)

 

혹은 위와 같이 구독하여 이벤트를 처리하는 Observer 역할 또한 할 수 있습니다!

 

다만, PublishSubject는 구독된 시점 이후에 생성된 이벤트만 구독자에게 전달하기 때문에 구독자가 구독을 시작하기 이전에 방출한 이벤트를 Observer에게 전달되지 않습니다.

 

그래서 위 코드에서는 next 이벤트로 '3'을 방출하여도 출력되지 않죠!

 

이해를 돕기 위해 아래 예제를 살펴보겠습니다.

 

let stringSubject = PublishSubject<String>()
let disposeBag = DisposeBag()

stringSubject.onNext("첫 번째 방출")

stringSubject.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)

stringSubject.onNext("두 번쨰 방출")

 

위와 같이 예시 코드를 작성하고 실행해보면 정확히 알 수 있습니다.

 

Subject를 생성한 이후 onNext 메소드를 통해 첫 번째 next 이벤트를 방출합니다.

 

하지만 아직 이 Subject를 구독하는 구독자가 없기 때문에 그대로 흘려보내죠.

 

이후에 subscribe 메소드를 통해서 구독하고 전달받는 이벤트를 출력하도록 한 이후 다시 onNext 로 이벤트를 방출합니다.

 

그러면 구독자가 구독을 한 상황에서 이벤트를 방출하기에 정상적으로 로그가 출력됩니다.

 

2. BehaviorSubject


다음으로 가장 많이 쓰이는 BehaviorSubject 에 대하여 알아보도록 하겠습니다.

 

BehaviorSubject 또한 PublishSubject와 작동 방식은 매우 유사합니다.

 

다만, BehaviorSubject는 Subject 생성시 기본값을 갖게됩니다.

 

마치 아래처럼요!

 

let behaviorSubject = BehaviorSubject<String>(value: "Default Value")

 

앞서 살펴봤던 PublishSubject는 비어있는 상태의 Subject를 생성하기 때문에 구독 이전에 이벤트가 방출되면 정상적으로 이벤트가 전달되지 않았죠.

 

하지만 BehaviorSubject는 기본값을 갖기 때문에 이를 Observer가 구독하자마자 next 이벤트로 전달해줍니다.

 

let behaviorSubject = BehaviorSubject<String>(value: "Default Value")
let disposeBag = DisposeBag()

behaviorSubject
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

// Output: Default Value

 

그래서 위와 같이 Subject에서 따로 이벤트를 생성하여 방출하지 않았으나 구독하면 기본적으로 next 이벤트로 생성시 기재한 기본값을 전달받게 됩니다.

 

let behaviorSubject = BehaviorSubject<String>(value: "Default Value")
let disposeBag = DisposeBag()

behaviorSubject
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

behaviorSubject.onNext("Second Emit")

behaviorSubject
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

 

위 코드의 결과값은 어떻게 될까요?

 

첫 번째 구독 이후 Second Emit 이라는 문자열 이벤트를 방출한 이후 다른 구독자가 또 구독을 하였습니다.

 

결과값은 아래와 같습니다.

 

/*
    Default Value
    Second Emit
    Second Emit
*/

 

즉, BehaviorSubject는 생성시 지정한 기본값을 갖지만 이후에 새로운 이벤트가 발생하면 가장 최근에 발생한 이벤트를 기본값으로 새로운 구독자에게 전달합니다.

 

따라서, Subject 사용시 기본값을 가져야 할 필요가 있을 경우 BehaviorSubject를 이용합니다.

 

3. ReplaySubject


 

세 번째는 ReplaySubject 입니다.

 

앞서 살펴본 BehaviorSubject는 최신 event 하나를 저장하고 있다가 구독자가 구독하면 이를 바로 전달합니다.

 

따라서 최근 이벤트 "하나" 만 저장해놓기 때문에 이전에 방출되었던 이벤트들은 모두 사라지게 됩니다.

 

ReplaySubject 는 BehaviorSubject와 매우 유사하지만 최근 방출된 이벤트를 몇 개 저장해놓을지에 대한 유연성을 갖습니다.

 

만일 3 개의 이벤트를 저장해놓았다가 새로운 구독 행위가 발생하면 구독자에게 바로 최근 3 개 이벤트를 바로 전달할 수 있습니다.

 

let disposeBag = DisposeBag()
let replaysubject = ReplaySubject<String>.create(bufferSize: 3)

let dummyData = ["환", "영", "합", "니", "다"]

dummyData.forEach { replaysubject.onNext($0) }

replaysubject
    .subscribe(onNext: {
        print($0, terminator: " ")
    })
    .disposed(by: disposeBag)

// Output: 합 니 다

 

위와 같이 ReplaySubject는 Subject 생성시 몇 개의 이벤트를 저장하고 있을지를 결정하는 버퍼 사이즈를 인자로 전달합니다.

 

위 예시에서는 3개의 이벤트를 저장하기 위해 3을 전달하였으며 그 결과 구독을 하니 3개의 문자가 next 이벤트로 전달되었습니다.

 

버퍼는 메모리에 저장되기 때문에 필요 이상의 큰 버퍼를 사용하는 것은 지양해야 합니다.

 

4. AsyncSubject


마지막 Subject는 AsyncSubject 입니다.

 

앞서 살펴봤던 Subjects 와는 달리 이벤트를 전달하는 시점에 확연한 차이가 있습니다.

 

PublishSubject, BehaviorSubject, ReplaySubject는 이벤트를 방출하면 그 즉시 구독자에게 이벤트가 전달됩니다.

 

하지만 AsyncSubjectCompleted 이벤트가 전달되기 전 까지는 어떠한 이벤트도 구독자에게 전달하지 않습니다.

 

Completed 이벤트가 전달되면 그 시점에서 가장 최근에 전달된 이벤트 하나만 next 로 구독자에게 전달합니다.

 

let disposeBag = DisposeBag()
let asyncSubject = AsyncSubject<Int>()

asyncSubject
    .subscribe(onNext: { print ($0)} )
    .disposed(by: disposeBag)

asyncSubject.onNext(1)
asyncSubject.onNext(2)
asyncSubject.onNext(3)

asyncSubject.onCompleted()

 

위와 같이 예시 코드를 작성해봤습니다.

 

Int 타입의 AsyncSubject을 만들고 구독을 진행합니다.

 

이후 1, 2, 3을 순차적으로 next 로 방출한 이후 Completed 를 방출하였습니다.

 

그 결과 3 만 출력되었습니다.

 

이를 통해 Completed 이벤트를 전달한 시점에서 가장 최근 next 이벤트가 구독자에게 전달되는 것을 확인할 수 있습니다.

 

만일, Completed 이벤트가 아닌 Error 이벤트가 전달된다면 이 때는 최근 이벤트를 next로 전달하지 않고 Error 이벤트만 전달된 이후 종료됩니다.

 

Relays


 

Relay 는 RxRelay에 포함되어 있고 RxCocoa를 import하여 사용합니다.

 

RxSwift에서는 두 가지 종류의 Relay를 제공합니다.

 

Relay는 Subject와 매우 유사한 특징을 가지고 있으며, 내부에서 Subject를 Wrapping 하고 있습니다.

 

Relay는 Subject와 마찬가지로 이벤트를 전달받아 이를 구독자에게 전달합니다. 다만 오직 Next 이벤트만 전달이 가능합니다.

 

CompletedError 유형의 이벤트는 전달을 받지도, 하지도 않습니다.

 

그래서 Subject와는 달리 구독자가 Dispose 되기 전 까지는 메모리에서 해제되지 않습니다.

 

이러한 특성으로 UI Event 처리에 많이 사용되고 있습니다.

 

PublishSubject를 래핑한 것이 PublishRelay, BehaviorSubject를 래핑한 것이 BehaviorRelay이며 두 Subject의 특성을 유지합니다.

 

let disposeBag = DisposeBag()

let publishRelay = PublishRelay<String>()
let behaviorRelay = BehaviorRelay<String>(value: "Default Value")

publishRelay.subscribe(onNext: { print($0) } )
behaviorRelay.subscribe(onNext: { print($0) } )

publishRelay.accept("First Emit")
behaviorRelay.accept("Second Emit")
/*
    Default Value
    First Emit
    Second Emit

 

위와 같이 PublishSubject, BehaviorSubject의 특성을 유지하며 이벤트를 방출합니다.

 

또한 Subject와는 달리 accept를 통해서 next 이벤트를 전달하게 됩니다.

 

error 이벤트와 complete 이벤트를 보낼 수 없고 오로지 accept만 처리할 수 있습니다.

 

즉, error도 발생하지 않고 complete도 발생하지 않습니다.

 

Subject는 complete 혹은 error 이벤트가 발생하면 그 스트림이 죽는 반면 relay는 위 두 이벤트를 무시하기 때문에 스트림이 종료되지 않습니다.

 

한번 시작된 스트림은 종료되지 않는다는 특성 으로 UI 처리 목적으로 많이 이용됩니다.

 

Driver


 

Subject를 wrapping 하여 relay 가 되었듯 Observable을 wrapping 하여 Driver로 사용합니다.

 

즉, Observable이 error 혹은 complete를 무시하고 싶을때는 Driver로 변경하여 사용합니다.

 

pwTextField.rx.text
        .subscribe(onNext: {
            viewModel.passwordInput
        })
        .disposed(by: disposeBag)
pwTextField.rx.text
        .asDriver()
        .drive(onnext: {
            viewModel.passwordInput
        })
        .disposed(by: disposeBag)

 

 

asDriver() 메소드를 통해 Observable을 driver로 변경하여 사용하며 driver로 변경시, subscribe()가 아닌 drive() 메소드를 통해 스트림 이벤트를 처리합니다.

 

driver 또한 relay 와 같이 MainScheduler에서 실행되므로 observeOn(MainScheduler.instance) 와 같이 스레드를 전환해 줄 필요가 없습니다.

 

이렇게 보면 driverrelay 는 마치 ObservableSubject 처럼 매우 유사하죠.

 

Subject와 Observable을 UI 처리 목적으로 죽지 않는 스트림을 제공하기 위해 생성된 것이 Driver 와 Relay 입니다.

'iOS' 카테고리의 다른 글

[iOS] [Swift] RxCocoa 맛보기  (0) 2020.11.26
[iOS] [Swift] RxSwift Operators Example  (0) 2020.11.20
[iOS] [Swift] RxSwift Observable & Observer, Disposable  (0) 2020.11.19
[iOS] Blur Effect in iOS  (0) 2020.11.11
LLVM (Low-Level-Virtual-Machine)  (0) 2020.10.31