2.1 TCA

TCA를 알기 위한 준비는 끝난 것 같으니, 본격적으로 TCA에서 가장 기본이 되는 개념부터 알아봅시다.

TCA는 현재 상태가 어떤지 파악하고 이를 관리하기 쉽게 하기 위하여 고안된 단방향 아키텍처입니다. 우리는 여기서 단방향 아키텍처라는 부분에 집중할 필요가 있습니다.

MVVM 작동 방식

MVVM 작동 방식

예를 들어 간편한 선택지였던 MVVM 패턴을 먼저 살펴보겠습니다. MVVM 또한 단방향 아키텍처로서 ViewViewModel의 상태를 관찰하고 해당 상태에 따라 업데이트됩니다. View는 자기 자신을 렌더링하고 유지하기 위해 ViewModel 에게 자신의 상태와 로직을 전달합니다. 따라서 View는 그 상태와 로직이 어떤 방식으로 변형되는지 알지 못해야 합니다. 그러나 SwiftUI와 같은 양방향 데이터 바인딩을 지원하는 프레임워크를 사용하면 ViewViewModel에서의 요청을 받아와서 추가적인 작업을 해야 하는 경우가 발생합니다. 이를 방지하기 위해 @Published 속성들에 대한 엄격한 캡슐화를 지향하는 사람들도 있으나, 이에 대한 일관적인 가이드라인은 없었습니다. 즉, 팀과의 협업 중 코드 작성 시에 유의하지 않는다면, 나중에 언제 어디서 상태가 바뀌는지 알기 파악하기 어려운 문제를 야기합니다.

이와 달리 TCA는 상태 변화에 대한 일관적인 가이드라인을 컴파일 단계에서부터 확보합니다. 어떤 개발자든 TCA를 적용한다면 상태 변화를 일으키는 Action 정의, Action에 대한 View에서의 .send() 를 거쳐야 하는 것이죠.

TCA 작동 방식

TCA 작동 방식

위 그림과 같이 TCA는 원래의 목적을 달성하기 위해 Single Source Of Truth(단일 진실 공급원)를 따르는 철저한 단방향 구조를 선택했습니다. Single Source Of Truth는 애플리케이션의 상태나 데이터에 대한 유일한 출처를 가지는 것을 의미합니다. 이로써 모든 상태 변경의 출처가 명확해지며, 그 변경이 예측, 추적 가능해지는 것이죠.

TCA에서는 변동을 추적하고 의도하지 않은 변경에서 지켜내야할 상태를 State라고 합니다. 사용자가 View를 통해 어떠한 작업이나 알림, 이벤트를 트리거 하면 연결된 ActionReducer 내에 구현된 함수로 State를 변화시키는 Effect를 반환합니다. 이때, 그 작업이 크게 복잡하지 않다면 바로 상태를 변화시킬 수도 있고, 때로는 네트워크 통신과 같은 비동기적인 작업이 필요한 경우가 있을 수 있습니다. 후자의 경우에는 통신을 실행하는 데에 필요한 환경과 자원이 필요하고 우린 이를 “의존성(Dependency)을 갖게 된다”라고 표현합니다. 또한 이때, 이 작업은 성공할 수도, 실패할 수도 있습니다. 보통 실패를 의도하는 경우는 없기에 성공한 경우를 Effect, 예상치 못한 경우를 Side Effect라고 합니다. 이렇게 도출된 Effect에 따라 다시 Action을 선택해 State를 변경합니다. 이렇게 변경된 State는 다시 뷰에 영향을 주게 되는 흐름으로 TCA는 작동합니다.

이러한 단방향 흐름은 애플에서 제안하는 흐름과도 일맥상통하는 부분이 있는데, 그 이유는 TCA가 SwiftUI를 닮으려고 노력하기 때문입니다. body를 구현함으로써 Reducer를 정의하거나, Dependency 를 프로퍼티 래퍼로써 사용하는 점과 같이 곳곳에서 그 흔적을 찾을 수 있습니다.

이 장에서는 State에 대해 먼저 알아보고 이를 View와 연동해 주는 Action, 그리고 Reducer까지 순차적으로 알아보려 합니다. 또한 TCA는 2019년에 처음 나와 지난 몇 년간 많은 수정 사항을 거쳐 마침내 1.0 버전이 릴리스 되었습니다. 기본 개념과 작동 방식 역시 조금의 변화가 생겼기에 지금은 어떻게 사용되는지 간단한 카운터 앱을 통해 확인해 봅시다.


2.2 앱의 상태: State

StateReducer의 현재 상태를 갖는 구조체입니다. 비즈니스 로직을 수행하거나 UI를 그릴 때 필요한 데이터에 대한 설명을 나타낼 때 사용됩니다.

버튼을 눌러서 어떤 숫자를 +1 해주거나 -1 해줘야 하는 간단한 View를 만들어야 한다고 해봅시다.

그 역할을 맡을 count라는 변수가 필요합니다. TCA에서 이를 사용하기 위해 아래와 같이 간단하게 해당 기능을 하는 State를 정의할 수 있습니다.

struct CounterFeature: Reducer {
    // 기능을 구현하기 위한 변수를 State에 만들어줍시다.
    struct State: Equatable {
        var count = 0
    }
    
    enum Action: Equatable {/* code */}
   
    var body: some ReducerOf<Self> {/* code */}
}

이렇게 만든 ReducerView와 연결해 봅시다.