SwiftUI TCA (2) - Effect


Effect의 구현과 활용 - Action에 따른 결과 : Effect

Effect는 Reducer의 액션이 반환하는 타입으로, 액션을 거친 모든 결과들을 칭한다. 그 중 외부에서 어떠한 처리가 일어나서 얻게된 예상과 다른 결과물들을 Side Effect라고 한다.

 

Effect는 외부 시스템과 상호작용하는 작업을 나타내는데, 이를 통해 앱의 State가 변경된다. State를 직접 변경할 때의 Action과 달리, Effect는 비동기적인 작업을 수행하고 그 결과를 Action으로 반환하여 State에 반영하기 위해 사용 된다.

 

즉, Effect는 특정 Action을 실행한 후 그 결과에 따라 새로운 Action을 생성하고 이를 통해 State를 업데이트하는 역할을 담당한다. 네트워크 호출, 데이터 로딩, 외부 서비스와의 교류 등 다양한 비동기 작업이 Effect로 분류될 수 있다.

 

Effect의 역할

  • 비동기 작업 관리 : 네트워크 요청, 데이터 로딩, 파일 다운로드 등 다양한 비동기 작업을 Effect를 통해 관리할 수 있다.
  • Side Effect 분리 : Effect는 순수 함수형 프로그래밍의 원칙에 따라 Side Effect를 배제한다. 이를 통해 코드에서 State 변화를 일으키는 부분과 Side Effect를 다루는 부분을 명확하게 분리함으로써 코드의 가독성과 추론력이 향상되며, 테스트와 디버깅 과정이 용이해 진다.
  • 취소 및 에러 핸들링 : Effect는 비동기 작업의 성공, 실패 및 중단을 관리하는데 사용된다. 예를 들어, 네트워크 요청 중에 발생한 오류를 적절하게 처리하고 State를 업데이트 할 수 있다.
  • 순서 보장 : TCA의 Effect는 순차적으로 실행되며 그 순서가 보장된다. 이로써 State 변화에 관련된 Side Effect를 적잘하게 처리하면서도 예측 가능한 결과를 얻을 수 있다.

순수 함수적인 Effect

순수 함수는 주어진 입력에 대해 항상 동일한 출력을 반환하고, 외부 상태를 변경하지 않으며, 부수 효과(Side Effect)가 없는 함수를 의미한다. 그렇기 때문에 순수 함수 자체로는 비동기 작업이나 Side Effect를 처리할 수 없다.

 

그러나, TCA의 Effect는 이 문제를 해결하기 위해 특별한 방식을 사용한다. Effect는 앱의 상태를 직접 변경하지 않고 비동기 작업을 수행 후 그 결과를 새로운 Action으로 반환하는 역할을 한다. 이렇게 생성된 Action은 Reducer에서 처리되어 State를 업데이트

 

네트워크 요청 같은 비동기 작업을 처리하는 경우, Effect가 요청을 수행하고 결과 데이터 또는 오류 정보 등 포함하는 새로운 Action을 생성하여 반환한다. 이 Action은 다시 Reducer에서 받아 상태 업데이트 로직을 수행

 

즉, TCA에서 Effect는 Side Effect와 같은 비동기 작업도 순수 함수적인 방식으로 처리할 수 있도록 설계되었다. 그래서 Effect 자체가 순수 함수라기 보다 순수 함수적인 방식으로 Side Effect를 관리하고 제어하는 역할을 하는 것!

 

카운터를 증가시키는 앱의 예제로 간단하게 이해해보자

import ComposableArchitecture

struct CounterState: Equatable {
    var count: Int = 0
    var isTimerRunning: Bool = false
}

enum CounterAction: Equatable {
    case increment
    case timerTicked
    case startTimer
    case stopTimer
}

State를 정의하고 Action도 같이 정의 해주면 어떤 행동을 할건지에 대해서 현재 카운터값을 구할 준비가 된다.

struct CounterEnvironment {
    var mainQueue: AnySchedulerOf<DispatchQueue> // 메인 스케줄러
}

Environment는 비동기 작업이나 외부 시스템과의 상호 작용을 처리하는데 사용되고 타이머는 일정 시간마다 Effect를 통해 실행되도록 할것이다 ㅎㅎ

let counterReducer = Reducer<CounterState, CounterAction, CounterEnvironment> { state, action, environment in
    switch action {
    case .increment:
        state.count += 1
        return .none
        
    case .timerTicked:
        state.count += 1
        return .none
        
    case .startTimer:
        state.isTimerRunning = true
        // 1초마다 timerTicked 액션을 발행하는 타이머 효과 생성
        return Effect.timer(id: "timer", every: .seconds(1), on: environment.mainQueue)
            .map { _ in CounterAction.timerTicked }
        
    case .stopTimer:
        state.isTimerRunning = false
        // 타이머 중지
        return .cancel(id: "timer")
    }
}

그 다음 Effect를 사용해 타이머를 관리하도록 해줄 수 있다.

 

이렇게 오늘은 기초 개념 정도만 잡아 보자 다음 시간에는 TCA에서 데이터를 바인딩하는 방식에 대해서 한번 알아보려고 한다 🙂

아따 아직 많이 어려운 것 같지만 꾸준히 공부해서 이해하자!