RxSwift에서 PublishRelay, BehaviorRelay를 왜 사용하는 걸까?

오늘은 이전에 알아봤던 Driver와 또 다른 특징을 가지고 있는 Relay에 대해서 알아보려고 한다 :) 런닝 앱을 구현하면서 이해하고 있는데 생각보다 유용하게 사용하고 있어 따로 정리를 해보도록 하자!

 

Relay란 무엇일까.

Relay는 상태 관리 및 이벤트를 전달하는데 특화되어 있는 Observable이라고 생각하는게 조금 이해하는게 쉽다. 대신 일반적인 Observable과 달리 종료되지 않고 에러를 방출하지 않는다는 중요한 특징이 있다. RxRelay에서 제공하는 기능이지만 RxCocoa를 임포트해 사용할 수 있다.

 

그래서 일반적으로 Observable을 사용해 onNext를 사용하는 방식이 아닌, accept를 통해 Dispose되기 전까지 동작해 UI에 사용된다고 이해하면 된다!

 

이 Relay 중에서 가장 자주 쓰이는 두가지를 알아보려고 한다.

  • PublishRelay
  • BehaviorRelay

PublishRelay

먼저 PublishRelay는 새로운 이벤트가 발생할떄 마다 모든 구독자에게 해당 이벤트를 전달하는 역할을 담당하고 초기값이 없어 구독 후에 발생한 이벤트만 수신한다!

 

그렇다면 유사한 PublishSubject를 쓰면되지 왜 PublishRelay를 사용하는 걸까?

 

여기에는 여러가지 선택적인 특징이 있다. 바로바로 에러처리와 종료 가능 여부에 따라 선택해 사용한다!

에러처리에서 차이점은 PublishSubject는 에러 이벤트를 방출할 수 있다. onError이벤트를 통해 에러를 전달하고 이 에러가 전달되면 해당 PublishSubject는 종료된다. 이후로는 더이상 이벤트를 방출할 수 없고 모든 구독도 해제되는 특징이 있다!

 

PublishRelay는 에러 이벤트를 방출하지 않는다. 따라서 onError와 같은 이벤트를 처리할 필요가 없고 PublishRelay는 종료되지 않아 계속해서 이벤트를 방출할 수 있다는 특징이 있다.

 

두번째로 종료 가능여부의 차이점이 있다.

PublishSubject는 onCompleted 이벤트를 통해 스트림을 종료시킬 수 있다. 스트림이 종료되면 더이상 이벤트를 방출하지 않고 구독도 해제된다.

 

PublishRelay는 앞서 개념을 얘기했던 Relay의 특징과 같이 종료되지 않고 지속적으로 이벤트를 방출하는 상태를 유지하는데 적합하다.

 

그렇다면 이런 다른 특징이 있다면 PublishRelay를 왜 사용해야하는 걸까? 여러가지 선택 옵션이 있지만 내가 PublishRelay를 선택해 구현한 이유는 다음과 같다.

 

PublishRelay 선택한 이유

안전한 상태관리

PublishRelay는 에러와 종료 이벤트를 다루지 않기 때문에 상태 관리에 적합하다. 상태 관리를 할 때는 보통 에러가 발생하거나 스트림이 종료되어야하는 일이 없어야하는데 PublishRelay 이런 측면에서 더 적합하게 사용이 가능하다는 점

 

간결한 코드 구현

에러와 종료 이벤트를 다루지 않기 때문에 이런 측면에 있어서 코드가 더욱 간결해지고 상태 관리 로직이 단순해지는 장점이 있다.

 

UI 이벤트 처리

PublishRelay는 주로 UI 이벤트를 처리하는데 사용했는데 UI 이벤트 처리는 보통 에러나 종료를 다루지 않는다. 그렇기 때문에 PublishRelay가 더 적합하다고 생각해 사용하게 되었다.

 

PublishRelay, PublishSubject 의 간단한 예제를 토대로 차이점을 정리하고 넘어가보자

let subject = PublishSubject<String>()

subject.onNext("Hello")
subject.onError(SomeError()) // 에러 발생, 이후 이벤트 전달 불가
subject.onNext("World") // 이 이벤트는 전달되지 않음

subject.subscribe(
    onNext: { print($0) },
    onError: { print("Error: \\($0)") },
    onCompleted: { print("Completed") }
)

먼저 PublishSubject를 사용했을때 onError, onCompleted를 사용해 에러 및 종료 관련 이벤트를 다루기 때문에 에러나 종료되어야 하는 상황 즉 , 네트워크 요청이나 스트림이 종료될 수 있는 상황에서 사용하는 것이 적합하다.

let relay = PublishRelay<String>()

relay.accept("Hello")
relay.accept("World") // 계속해서 이벤트를 전달할 수 있음

relay.subscribe(onNext: {
    print($0)
})

위에서 PublishSubject와 달리 PublishRelay는 별도 에러처리나 종료 처리 없이 계속해서 이벤트를 전달할 수 있다는 특징이 다르다는 점을 확인할 수 있다.

 

내 프로젝트에서 런닝을 뛰기 시작했을때 위치, 시간 등 실시간으로 종료하기 전까지 계속 이벤트를 전달 받아 처리해야하기 때문에

이렇게 PublishRelay 를 활용해 전달 받는 방식을 선택했다고 보면 된다 🙂

 

BehaviorRelay

자 이제 두번째로 BehaviorRelay에 대해서 알아보도록 하자

 

이것도 마찬가지로 BehaviorSubject를 사용하면 되지 왜 BehaviorRelay를 사용하는 걸까? 둘 다 현재의 상태를 저장하고 새로운 구독자에게 최신값을 전달하는 기능을 가지고 있는데

 

BehaviorSubject와 BehaviorRelay의 주요 차이점

위에 PublishRelay, PublishSubject의 선택했던 이유 중 에러 및 종료 처리를 별도로 하지 않는다는 차이가 같기 때문에 더 길게 작성하지는 않겠다 ㅎㅎ

 

중복되는 에러 및 종료 처리에 대한 내용은 빼고 새로운 차이점을 살펴보면

초기값이다. BehaviorSubject는 생성 시점에 초기값을 설정할 수 있고 이 초기값은 구독자가 구독할 때 바로 전달된다. 그리고 마지막으로 방출된 값을 기억해 새로운 구독자에게 전달하게 된다.

 

BehaviorRelay도 초기값을 설정할 수 있고 이후 구독자가 구독할 때 항상 최신값을 전달한다. BehaviorRelay는 주로 상태 관리를 위해 사용된다고 보면 된다!

let subject = BehaviorSubject(value: "Initial Value")

subject.onNext("Updated Value")

subject.subscribe(
    onNext: { print($0) },
    onError: { print("Error: \\($0)") },
    onCompleted: { print("Completed") }
)

subject.onNext("Another Update")
subject.onError(SomeError()) // 에러 발생, 이후 이벤트 전달 불가
subject.onNext("This won't be printed") // 전달되지 않음

BehaviorSubject의 사용 예시를 살펴보면 에러 및 종료 이벤트를 다루고 있어 네트워크 요청이나 종류될 수 있는 스트림에서 주로 사용되고있다.

 

또한 구독자에게 마지막으로 방출된 값 또는 초기값을 전달하고, 이후 이벤트를 계속해서 방출할 수 있다는 특징이 있지만, 오류나 종료 이벤트가 발생하면 종료된다는 점이 있다.

let relay = BehaviorRelay(value: "Initial Value")

relay.accept("Updated Value")

relay.subscribe(onNext: {
    print($0)
})

relay.accept("Another Update") // 계속해서 값 업데이트 가능

마찬가지로 오류 및 종료 이벤트가 존재하지 않기 때문에 계속해서 값을 업데이트하는 BehaviorRelay가 Subject와의 차이점을 살펴볼 수 있다 🙂

 

오늘은 이렇게 현재 구현하고 있는 런닝앱에서 사용했던 RxSwift 중 Relay에 대해서 알아보는 시간을 가졌다 🙂

 

앞으로 더 자세하게 하나하나 뜯어보는 과정을 거쳐 하나씩 내것으로 만들도록 하자!!

오늘은 여기까지!