본문 바로가기
Unity/UniRx & UniTask

Observable 사용 방법 <Subject>

by Pretty Garbage 2023. 1. 24.

Observable을 활용하는 방법을 소개하고자 합니다.

 

1. Subject

Subject는 Observer 패턴에 있어서 아주 핵심이 되는 개념이고 이는 UniRx에도 해당이 됩니다. 단어의 뜻처럼 주제

어떠한 주제에 대한 스트림을 흘릴 것이냐?를 정의하는 부분입니다.

 

Subject<T>는 IObserver<T>와 IObservable<T> 두 가지를 구현하는 객체입니다. 구독을 등록한 Observer에게

메시지 발행 타이밍에 브로드 캐스트 방식으로 알립니다.

 

예를 들면

var subject = new Subject<int>();

//1이라는 값을 발행하는 메시지를 보낼거야
subject.OnNext(1);

//OnNext를 받으면 디버그 로그에 출력
//OnError를 받으면 에러메시지를 디버그 로그에 출력
//OnComplete를 받으면 완료 메시지를 디버그 로그에 출력
subject.Subscribe(
    x => Debug.Log(x.ToString()),
    ex => Debug.Log(ex.ToString()),
    () => Debug.Log("OnComplete"));

subject.OnNext(2);
subject.OnNext(3);

subject.OnCompleted();

subject.Dispose();

이렇게 되었을 경우 결론만 말하자면 유니티 콘솔창에 찍히는 로그는 2와 3 그리고 OnComplete만 출력하게 됩니다.

 

이유는 OnNext(1)을 할 때 구독을 하기 전이었고, subject에 구독을 정의한 뒤에는 OnNext(2), OnNext(3) OnCompleted()만이 Subject를 통해서 방행되었을 뿐입니다.

 

UniRx에서 제공되는 Subject는 여러가지가 있습니다. 앞서 소개한 가장 기본이 되는 Subject도 있지만 기능이 조금 더 추가된 Subject도 있습니다.

 

2. Behavior Subject

Behavior Subject<T>는 Subject<T>에 마지막 메시지를 하나만 캐싱하는 기능이 들어간 Subject입니다.

캐싱이된 메시지는 새로운 구독처리가 등록될 때 발행됩니다.

새로운 구독처리를 하는데 직전에 발행된 값이 필요하거나 할 때 유용합니다.

(주의 : OnCompleted가 발행되면 캐싱된 값도 발행이 중지됩니다.)

 

var subject = new BehaviorSubject<int>(0);

//1이라는 값을 발행하는 메시지를 보낼거야
subject.OnNext(1);

//OnNext를 받으면 디버그 로그에 출력
//OnError를 받으면 에러메시지를 디버그 로그에 출력
//OnComplete를 받으면 완료 메시지를 디버그 로그에 출력
subject.Subscribe(
    x => Debug.Log(x.ToString()),
    ex => Debug.Log(ex.ToString()),
    () => Debug.Log("OnComplete"));

subject.OnNext(2);
subject.OnNext(3);

subject.OnCompleted();

subject.Dispose();

아까와 매우 유사한 코드이지만 이번에는 처음 코드와는 다른 결과를 보여줍니다. Behavior Subject는 앞서 말했듯 이전 값을 캐싱하고 보여주기 구독처리를하면 발행해주기 때문에 결과는

1,2,3,OnComplete가 순서대로 발행됩니다. 

 

3.ReplaySubject

ReplaySubject<T>는 BehaviorSubject<T>와 유사하지만 조금 더 옵션이 많은 버전입니다.

ReplaySubject 생성자에 3가지의 파라미터를 넘겨서 생성할 수 있는데

int bufferSize 이전 메시지를 몇개 캐싱하는가 디폴트 값은 int.Max (그러면 수많은 값을 캐싱할테니 잘 조정하자)
TimeSpan window 이전 메시지를 얼마동안 오래(시간적으로) 캐싱하는가 기본값은 TimeSpan.Max
IScheduler scheduler window 즉 시간에 대한 계산은 어떤 스케쥴러에서 관리되는지? 기본값은 current ThreadScheduler

bufferSize와 window는 기본값이 Max이기 때문에 잘 조절하고 사용하는 것이 좋습니다.

ReplaySubject<T>는 BehaviorSubject<T>와 달리 OnCompleted 와 OnError 메시지도 캐싱합니다.

 

var subject = new ReplaySubject<int>(bufferSize: 3);

for (int i = 0; i < 10; i++)
{
    subject.OnNext(i);
}

subject.OnCompleted();

subject.Subscribe(
    x => Debug.Log(x),
    ex => Debug.Log(ex),
    () => Debug.Log("OnCompleted")
);

subject.Dispose();

bufferSize를 3으로 설정했기 때문에 구독이 늦어도 7 8 9 그리고 OnCompleted 메시지가 발행됩니다.

 

4. AsyncSubject

AsyncSubject<T>는 비동기 처리에 사용하기 위한 Subject입니다.

C#의TaskCompletionSource<T>와 비슷합니다.

https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.tasks.taskcompletionsource-1?view=net-7.0 

 

TaskCompletionSource<TResult> 클래스 (System.Threading.Tasks)

대리자에 바인딩되지 않은 Task<TResult>의 생산자 측면을 나타내고 Task 속성을 통해 소비자 측면에 대한 액세스를 제공합니다.

learn.microsoft.com

 

AsyncSubject<T>는 OnCompleted 메시지가 입력 될 때까지 결과를 출력하지 않는다는 특징이 있습니다.

AsyncSubject의 또 다른 특징은 Subscribe()하는 타이밍과 관계없이 결과가 떨어지면 필요한 메시지를 받을 수 있습니다.

 

이해가 안된다면 아래와 같은 예시를 보여드립니다.

private AsyncSubject<Texture> subject;
private void Start()
{
    subject = new AsyncSubject<Texture>();

    subject.Subscribe(SetTexture);

    StartCoroutine(LoadTexture());
}

private IEnumerator LoadTexture()
{
    var uwr = UnityWebRequestTexture.GetTexture("https://urlexample/1Q1Z1Zm.png");
    yield return uwr.SendWebRequest();
    
    subject.OnNext(DownloadHandlerTexture.GetContent(uwr));
    subject.OnCompleted();
}

private void SetTexture(Texture texture)
{
    var r = GetComponent<Renderer>();
    r.sharedMaterial.mainTexture = texture;
}

 

UnityWebRequestTexture를 통해서 url의 이미지를 다운로드후 AsyncSubject에 텍스쳐를 흘려주고 OnComplete를 해주면

OnCompleted가 호출되는 시점에 SetTexture가 실행이 되어 비동기적으로 Set 하게 됩니다.