SwiftUI 기반 MVI(TCA) 아키텍쳐

일단 여기서 언급하는 아키텍쳐는 클린아키텍쳐도, 모듈 기반 아키텍쳐도 아닌, 별개로 보셔도 됩니다.

먼저 안드로이드와 iOS의 비즈니스 로직을 동일하게 구성한다면, 팀원들간에 커뮤니케이션이 기존보다 개선되지 않을까? 였습니다.
그리고 UIKit보다는 SwiftUI로 구현했을 경우, 향후 안드로이드팀에서 Compose를 사용한다면 더욱 서로 다른 플랫폼간에 간극은 줄여들것이라고 생각하여, 아키텍쳐 구성할때 가장 큰 목표로 잡았습니다.

먼저 SwiftUI의 특징에 대해 가장 먼저 검토하게 되었습니다.

SwiftUI의 경우 State의 변화하게 되면 뷰를 다시 그리는 방식으로 되어있습니다. 조금 더 풀어보자면, State의 조작을 ObservableObject 프로토콜을 사용한 컨트롤러를 통해서 변경하게 되며, 변경 즉시, 대상의 화면을 다시 렌더링하게 되죠. 이는 MVVM에서 양방향보다는 단방향에 더 가깝다고 보게 되었습니다.

SwiftUI의 경우 State 변경 시, 화면을 새롭게 렌더링하는 특징을 가지고 있습니다.
특징을 활용하여 State를 변경하는 주체를 비즈니스로직으로 가져가면 데이터의 변경과 조작의 흐름을 단반향으로 흘러가게 됩니다.
이러한 이점을 이용하여 ObservableObject 프로토콜 사용한 컨트롤러를 추가하게 되었습니다.

단방향이 가지고 있는 이점

State > Action > Process > (Mutataion)State 순으로 흐르는 단방향 흐름은 개발자가 보다 직관적으로 데이터가 어떠한 액션에 변경되었고, State가 어떻게 변화하는지 보다 직관적으로 알수있게되어 개발자가 예상하지 못한 State 변경을 조기에 해결할 수 있습니다. 또한 Test와 동일한 State값을 유지하기때문에 State의 변화는 더욱 정직하게 보여 신뢰할 수 있게 됩니다.

비즈니스 로직과 사용자 이벤트에 대해 알아보도록 하죠. 사용자는 앱을 사용하면서 버튼 탭, 스크롤 등 다양한 이벤트를 만들고, 앱 내부적으로 이벤트를 받아 비즈니스 로직 및 State에 변화를 주게 됩니다.

일전에 팀내에서 Building Mobile Apps at Scale 스터디를 진행하게되었습니다. 그중에서 State의 관리부분을 다시 분석하게되었습니다.

State와 View와의 관계

SwiftUI 현재 View의 상태를 State로 표현이 가능합니다. 테스트 케이스 작성할 때도, State로 정의를 할 수 있습니다. 결국 View는 State의 의존성을 가지고 있으며, 비즈니스로직과 별개로 나뉠 수 있게됩니다.

View(Action)과 비즈니스와의 관계

앞서 이야기했던 Action을 정의하고 해당 액션을 비즈니스 로직을 전달하는 역할을 하게 됩니다. 그리고 비즈니스 로직은 State를 변경하여 사용자 액션에 응답하게 되는 구조가 되겠습니다.

위의 도식화에서 보이는 특이점 데이터가 한 방향으로 변경이 됩니다. View에서 시작하고 이벤트를 통해서 로직을 수행하고, 수행한 결과를 State를 변경하면서 View가 다시 렌더링 되는 결과입니다.

비즈니스 로직 담당 Intent / Reducer

우리는 저 비즈니스 로직을 담당하는 애들 뭐라고 하면서 좋을까? 라고 고민했습니다. 먼저 로직을 수행하는 처리하는 역할에 대해 정의해보겠습니다

  • View에게 상태 업데이트를 전달한다. 즉, State를 최종적으로 변경한다.
  • View에서 요청할 액션을 정의된 액션을 받는다. 해당 액션을 통해서 외부의 요소(네트워킹, 데이터 비교) 등을 통해서 결과를 요청하고 받는다.
  • 요청된 결과를 받고, State를 업데이트를 한다.

타 플랫폼에서는 이러한 역할을 하는 친구들을 찾아보기로 했습니다.

다만 리액트의 경우는 안드로이드의 Intent랑 다른 점은 State가 SingleState (앱 시작과 종료까지) 하나로만 사용한다는 개념입니다. 안드로이드 또한 State 변경을 Intent에서 할 수 있으며, 완전히 동일한 개념이 아니라, 가장 유사한 부분이라는 결론을 가지게 되었습니다.

TCA (The Composable Architecture)

이외에도 TCA라는 아키텍쳐가 있는데요. 여기서 TCA는 Redux 패턴과 더욱 유사합니다. SingleState 사용으로 Reducer라는 메서드내부에서 State 변경하는 역할을 두고 있군요.

결국 TCA, Redux, Intent도 MVI와 동일하거나 비슷하다

다시 정리하자면, MVI를 통해 얻고자 하는 목적은 다음과 같습니다

  • State 집중적으로 변경되는 시점에 대해 보다 직관적으로 관리하고 싶다.
  • State 에 변화를 줄수 있는 영역을 따로 관리하고 싶다.
  • State 와 Action에 관계를 통해서 보다 Testable한 코드를 작성하여 비즈니스 로직을 명시화 하고 싶다.

MVI 패턴의 대한 정의

M(Model): State와 Action을 정의하며 State는 struct type, Action을 enum 타입으로 한다.

V(View): State를 기준으로 화면을 렌더링이 되며, State 변경되면 변경된 부분을 업데이트하며, 사용자 액션을 Action의 형태로 Intent로 전달한다.

I(Intent): 화면에서 전달받은 Action를 받고 분리하여 비즈니스 로직을 수행한다. 수행된 결과를 State를 변경하여 화면의 업데이트 요청한다.

Intent를 보다 세부적으로 나누어보자

Intent는 State를 변경하는 역할이 가장 최우선입니다. 네트워크 통신하는 처리 등 그 외에 기능을 Action에 정의하면 할수록 우리가 원하는 State의 변화를 코드에서 보기 힘들어지게 될 것입니다.

여기서 Intent의 역할 중 외부요소를 분리해보죠. 외부의 요소에 따라 State가 변경되기때문에 SideEffect(Environment)라고 칭합니다.

Intent는 사이드 이팩트를 가지고 되고, 사이드 이팩트에게 요청하고 요청받은 결과값은 다시 Intent Action을 통해 전달하게 됩니다. 결과값을 Intent Action 으로 요청하는 이유는 도중에 에러가 발생하거나 취소시킬 수 있어야 하기 때문입니다.

직접 구현하는 것보다, TCA 라이브러리를 사용하여 해당 구현체를 구현하였습니다. MePreview로 확인 하실 수 있으시며, 해당 링크를 참고해주시길 바랍니다. 이제 우리는 화면과 비즈니스 로직에 대한 커뮤니케이션 방법에 대해 아키텍쳐적으로 풀어나가봤습니다.

0 Shares:
You May Also Like
Read More

각 핵심서비스의 독립

대부분의 앱의 첫 출시는 내부의 기능과 콘텐츠들은 매우 심플하고 복잡하지 않습니다. 그러나 사업이 확장되고, 더 많은 서비스가 등장하고,…