Combine이든 RxSwift든 리액티브 프로그래밍 프레임워크를 다루다보면 한번씩 마주하게 되는 문제가 몇가지 있다. 하나는 메모리 관리 문제고(보통 이건 초심자가 많이 겪는다), 또 하나는 배압(Backpressure) 문제다. 리액티브 프로그래밍에서 배압이란 무엇을 말하는가? 구독자와 게시자가 있다고 할 때, 구독자가 값(혹은 이벤트)을 소비하는 속도가 게시자가 값을 보내는 속도를 따라가지 못하는 상황을 일컫는다. 이게 원래는 유체동역학 용어(위키백과: Back Pressure)라는데, 프로그래밍 관점에서 리액티브 프로그래밍이라는게 값의 흐름이니 정말 적절한 용어가 아닌가 싶다.

By Engr.Nithin Mohandas - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=74922539
By Engr.Nithin Mohandas - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=74922539

여튼 이런 배압 문제는 일반적으로는 쉽게 발생하지 않는다. 일반적으로 리액티브 프로그래밍을 비동기 처리를 위해 사용하다보니 게시자의 값 송출이 감당하지 못할만한 상황이 자주 일어나지 않기 때문이다. 그렇기 때문에 앞에서 초심자가 많이 겪는 메모리 관리 문제보다는 좀 더 리액티브 프로그래밍을 헤비하게 사용하는 경우 맞닥뜨리기 쉽다(앱에서 발생하는 온갖 이벤트를 다 리액티브하게 처리를 한다던가…). 예를 들어 기기의 자이로 센서 등을 통해 값을 계속해서 전달받는 경우가 있다고 하자. (센서의 민감도에 따라 다르겠지만) 1초 사이에 어마어마하게 많은 값을 받게 될 수도 있다. 이런 경우에는 차라리 특정 시간차를 두고 그 사이에 온 최초 혹은 최후 혹은 어떤 하나의 값을 받아도 문제가 없을 것이다.


ReactiveX의 경우에는 throttle이나 debounce, buffer 등의 연산자를 활용하거나 경우에 따라 적절한 Cold/Hot Observable을 사용하라는 식의 지침이 있다(ReactiveX: Back pressure operators).

그렇다면 Combine의 경우에는 어떤 식으로 처리할 수 있을까? 우선, Combine도 Throttle이나 Debounce, Buffer와 같은 사전정의된 Publisher를 제공한다. 사실 이것들만 잘 활용해도 어지간한 케이스에는 대응할 수 있다.

하지만 게시자(Publisher)와 구독(Subscription)을 직접 구현하는 방법으로도 배압을 통제할 수 있다. Combine은 구독자(Subscriber)가 게시자로부터 값을 당겨오는 메커니즘을 사용한다. 이때 구독자는 게시자에게 Subscribers.Demand라는 구조체를 전달해 얼만큼의 값을 당겨올지 요청할 수 있다. 그리고 이 Demand를 조절하는 방식으로 값을 당겨오는 것을 통제해 배압 문제를 처리할 수 있는 것이다. 예를 들어 외부에서 Demand를 통제할 수 있는 커스텀 구독자를 만들고, 상황에 따라 이 구독자의 참조를 지닌 상태에서 값의 처리가 힘든 상황이 되면 잠시 값을 당겨오지 않도록 했다가 다시 준비가 되면 당겨올 수 있도록 하는 것이다.

이 때 주의할 것이 하나 있는데, Demand를 조절하는 방법에는 가산(add)만이 존재한다는 것이다. 예를 들어 이미 .max(5)와 같은 Demand가 동작을 하고 있다면, 3번째 값을 받고 .none을 전달해 더이상 값을 받지 않겠다고 해도 앞선 .max(5)에 따라 4번째, 5번째 값을 받고 나서야 .none이 효력을 발휘한다. 그렇기 때문에 직접적으로 Demand를 조절하고자 하는 경우에는 .max(1)과 같이 한번에 하나씩의 값을 받는 식으로 처리하는게 좀 더 유용할 것이다(무조건 이렇게 하라는 건 아니다!).