[iOS] Combine을 왜 사용할까?

Picnic·2024년 12월 8일

iOS

목록 보기
5/5
post-thumbnail

안녕하세요 Picnic🧃입니다.
오늘은 Combine에 대해서 공부한 내용을 정리하겠습니다.

이 글은 Combine에 대한 사용법을 작성한 글은 아닙니다.
Combine을 왜 사용하게 되었을까에 대한 개인적인 생각을 적은 글입니다.

바로 시작하겠습니다.

그 전에


먼저 Combine을 살펴보기 전에 이전에 제가 MVC 패턴과 MVVM 패턴을 보면서 생각했던 것이 있습니다.
MVC 패턴의 경우 Model의 변화를 ViewController에 Notification을 보내고, MVVM 패턴의 경우 ViewModel에서의 Model의 변경을 View에 알려 사용자에게 변경된 데이터를 보여줍니다.

이 때, “데이터의 변화가 있다는 것을 어떻게 역으로 알릴 수 있을까”라는 생각을 하게 되었는데요.
Model은 Controller를 소유하는 것도 아니고 ViewModel을 소유하는 것도 아니기 때문에 직접적으로 호출할 수 없을텐데 말이죠.

그에 대한 해답은 여러가지가 있습니다.
Delegate Pattern을 이용하거나(UITableViewDelegate와 같이 작업을 다른 객체에 넘기는 방식이죠!),
Closure를 할당하거나, KVO(Key-Value Observing), NotificationCenter 등을 이용하는 방식이 있죠.

하지만, Delegate 패턴의 경우 본래 한 객체가 해야 할 작업(또는 책임)을 다른 객체에 위임한다는 것이므로 Model이 해야할 일을 Controller에 넘기는 것도 이상하고 ViewModel이 해야할 일을 View에 넘기는 것도 이상하기 때문에 잘 사용하지 않을 것입니다.

Closure의 경우 Pure-Swift를 이용할 수 있기 때문에 objective-c 런타임에 의존하지 않고 선언적 프로그래밍이 가능하다는 장점이 있지만, 비동기 작업의 경우 반복되는 completion 지옥(콜백 지옥이라고도 하죠😦)에 빠질 수 있다는 단점이 있습니다.(후에 async-await과 같은 방안이 나와서 지금은 간단한 작업에 좋은 방법일 수 있겠네요!

KVO(Key-Value Observing) 은 NSObject를 상속하는 객체에만 적용 가능하기 때문에 objective-c 런타임을 이용하게 됩니다.

NotificationCenter의 경우 비동기 작업을 할 수 있지만 전역적인 알림에 더 어울리는 것 같습니다. 또한 전역적으로 사용하기 때문에 흐름을 추적하기 어렵다는 단점도 있습니다.

Combine


그리고 오늘의 주제인 Combine을 사용하는 방법도 있습니다.
Combine은 무엇일까요?

애플에 따르면 Combine은 다음과 같습니다.

Combine 프레임워크는 시간이 지남에 따라 값을 처리하기 위한 선언적 Swift API를 제공합니다.
이 값들은 많은 종류의 비동기 이벤트를 나타낼 수 있습니다.
Combine은 publisher가 시간이 지남에 따라 바뀔 수 있는 값을 노출하고, subscriber는 publisher로부터 그 값을 받을 수 있다고 선언합니다.

저는 처음 Combine을 알아볼 때 그저 ‘많이들 쓰니까 좋은 거구나…’ 하는 생각으로 사용을 했습니다.
그러다가 다른 방법들도 있는데 왜 Combine인가라는 생각을 하게 됐어요.

Combine이 비동기 이벤트를 쉽게 처리할 수 있도록 도와주는 것은 알겠는데 그러면 ‘비동기 이벤트들이 왜 중요하고 선언적이라는 것은 무슨 의믜를 가지고 있을까’ 라는 생각을 문득 하게 되었습니다.

선언형 프로그래밍은 명령형 프로그래밍과 달리 HOW가 아니라 WHAT에 집중한다고 합니다.
하지만 선언형 프로그래밍은 결국 내부적으로는 명령형 알고리즘이 동작하고 있다고 합니다.
선언을 하기 위해선 어떻게 해당 선언이 동작하는지 알고 있어야 하기 때문이죠.
즉, 결국 선언형 프로그래밍은 명령형 알고리즘이 추상화 되어서 제공되는 것으로 생각할 수도 있습니다.

예를 들어 numbers라는 배열에 .filter 메서드를 사용하면 내가 원하는 조건의 값들을 얻을 수 있겠다라는 것을 fileter라는 이름에서 느낄 수 있습니다. 하지만 이 .filter 메서드 내부에서는 반복을 통해 내가 설정한 조건을 검사하는 명령형 프로그래밍이 포함되어 있다는 것이죠!

결국 선언적 프로그래밍을 통해 전체적인 가독성과 추상화 수준을 높여 문제의 본질에 집중할 수 있도록 도와주는 효과를 가질 수 있습니다.

또한 Combine의 정의에는 “시간이 지남에 따라 값을 처리하기 위한” 이라는 말이 있습니다.
이를 다시 말하면 “반응형”이라고 말할 수 있을 것 같습니다.
그렇다면 Combine은 반응형 프로그래밍을 위한 선언적 Swift API라고 생각할 수 있겠네요!

반응형 프로그래밍


그럼 반응형 프로그래밍은 뭘까요?

반응형 프로그래밍은 데이터의 흐름과 변경사항의 전파에 중점을 둔 선언적 프로그래밍 패러다임입니다.(위키피디아)

정의를 보니 Combine에 대해 조금 더 알 수 있을 것 같습니다!
결국 Combine은 데이터의 흐름과 변경사항의 전파에 중점을 두고 이를 처리하기 위한 선언적으로 만든 Swift API인 것이죠! 🙃

다른 분들은 모르겠지만 저는 여기까지 와서야 조금 이해하게 되었던 것 같습니다… 뭔가 답답함이 해소되는 순간이었달까요?
아무튼!

반응형 프로그래밍에서의 변경사항의 전파는
관련 데이터가 필요한 시점에 데이터를 요청하는 방식. 레거시 데이터가 있거나 중복 요청이 있을 수 있다.(Pull)에서 값이 변경될 때마다 변경된 값을 전파해서 사용. 언제나 변경된 최신의 데이터를 가지고 있다.(Push)로 전환되었습니다.

“필요할 때 데이터를 요청하여 내가 명령형으로 이렇게 해서 이렇게 하고…” 하는 방식에서 “언제나 최신의 데이터를 가지고 있고, 나는 이걸 할거야” 라는 방식으로 변경되는 것이죠.

이렇게만 봐도 코드가 간결하고 무엇을 하는지 목적이 보이는 듯한 느낌입니다.

그러면 반응형 프로그래밍이 왜 중요해서 이걸 만들었을까? 를 생각할 수 있죠.(전 바로 생각하진 못했지만 🫠)

그 중 한가지는 비동기 프로그래밍을 잘하기 위해서 입니다.

비동기 프로그래밍이 어려운 이유는 다음과 같습니다.

  1. 비동기 프로그래밍은 작성한 코드 순서대로 동작하지 않습니다.
  2. 언제 실행이 될지 예측할 수 없습니다.
  3. 호출한 순서대로 동작한다는 보장이 없습니다.
  4. 그래서 호출 당시의 값과 실제 실행되었을 때의 값이 그대로일 것이라는 보장이 없습니다.

그리고 사용자의 터치, 서버와의 통신 등도 모두 비동기 이벤트이기 때문에 개발자가 모두 예측하고 코드를 작성할 수는 없죠.

이를 Swift에서는 Publisher와 Subscriber를 통해 Combine을 구현합니다.
Publisher는 값을 내보내는 역할이고 Subscriber는 항상 값을 받는 역할이죠.
Publisher를 구독한 Subscriber는 언제나 Publisher가 내보내는 최신 값을 가질 수 있는 것이고, 개발자는 해당 값을 사용하면 됩니다.

돌아와서


그렇게 Combine이 왜 좋은지 어느정도 이해했으니 적용을 해보려고 했습니다.
처음에는 MVC 패턴에 적용을 해보려고 했는데 MVC 패턴에 Combine을 적용하는 글들은 거의 없는 것 같았습니다.

그래서 왜 그럴까에 대해서 생각을 하고, GPT에 물어보기도 하며 내린 가장 큰 결론은 다음과 같습니다.

  • Combine을 사용하면 Controller의 역할이 모호해집니다.
    Combine을 사용하면 Combine의 흐름 내에서 데이터가 다루어지기 때문에 Model의 변화를 View에서 바로 적용하게 됩니다.(Model과 View가 직접적으로 연결됩니다.) 이렇게 되면 중간에 있던 Controller의 역할이 모호해질 수 있습니다.
  • 또한 MVC 패턴은 명령형 데이터 흐름을 가집니다.
    MVC 패턴에서는 사용자가 입력을 처리하고, 해당 입력에 따라 Model을 업데이트하며, Model의 변화가 발생하면 이를 다시 View에 전달하여 View를 갱신하는 역할을 Controller가 수행합니다. 반면에 Combine은 선언형으로 이벤트 흐름을 다루기 때문에 데이터 흐름이 자동화됨에 따라 Combine에서 데이터를 처리하기 때문에 MVC 패턴과의 흐름과는 맞지 않고(MVC 패턴이 꼭 명령형이어야 한다는 것은 아니지만) 위와 마찬가지로 Controller의 역할이 모호해집니다.

그리고 MVC 패턴은 이전 포스팅에서 봤던 단점들 때문에 MVVM 패턴을 사용하게 되는 경우가 더 많습니다.
그리고 MVVM 패턴에서는 View가 ViewModel을 가지고, ViewModel이 Model을 가지는 단방향성 있기 때문에 Combine이 더욱 유용하다고 생각합니다.

View가 ViewModel의 데이터를 구독함으로써 ViewModel에서는 데이터를 가공하여 모델을 변경시키고 해당 모델의 변경을 View에서 감지하여 바로 적용할 수 있게 되는 것이죠.

결국 비동기 작업이 매우 많은 요즘은 이런 비동기 작업을 처리하는 것이 매우 중요해졌고, 이런 처리를 명령형이 아닌 선언형을 통해 작업함으로써 개발자가 더욱 명확하게 목표를 파악하고 데이터를 쉽고 유용하게 다룰 수 있도록 만들어주는 것이 Combine이라고 생각합니다.(결국 처음 나왔던 정의와 같지만 그 의미를 조금 알게 된 느낌이네요🙃)

또한 Swift로만 작성되어 있기 때문에 objective-c 런타임을 이용하지 않는다는 장점도 있습니다. (물론 다는 아니지만..!)

그러면 Combine에 대한 글은 여기서 마치겠습니다.

참고


[Combine]

[선언형 프로그래밍]

[반응형 프로그래밍]

[Dispatch]

0개의 댓글