Why This Work Exists
질문 포인트가 “TCA가 무엇인지” + “현재 샘플앱에서 어떻게 적용됐는지”였기 때문에, 개념 설명과 코드 매핑을 함께 제공한다.
TCA를 처음 보는 개발자가 왜/어떻게/어디를 먼저 봐야 하는지를 한 화면에서 이해할 수 있게 정리한 문서다.
2026-05-12
질문 포인트가 “TCA가 무엇인지” + “현재 샘플앱에서 어떻게 적용됐는지”였기 때문에, 개념 설명과 코드 매핑을 함께 제공한다.
아래 4줄만 먼저 기억하면 된다.
enum Action { case load, loaded(String) }
struct Effect {
let run: () async -> Action
}
func reducer(action: Action) -> Effect? {
switch action {
case .load:
return Effect {
let msg = (try? await fetchMessage()) ?? "fail"
return .loaded(msg)
}
case .loaded:
return nil
}
}
func fetchMessage() async throws -> String {
try await Task.sleep(nanoseconds: 300_000_000)
return "ok"
}
struct State {
var count = 0
var isLoading = false
var message = ""
}
enum Action {
case plus, minus
case loadMessageTapped
case messageLoaded(String)
}
// 핵심: reducer는 Effect(비동기 작업)를 반환
func reducer(state: inout State, action: Action) -> Effect<Action> {
switch action {
case .plus:
state.count += 1
return .none
case .minus:
state.count -= 1
return .none
case .loadMessageTapped:
state.isLoading = true
return .run { send in
Task {
let text = await fetchMessage() // async/await 사용
send(.messageLoaded(text)) // 결과를 Action으로 환원
}
}
case .messageLoaded(let text):
state.isLoading = false
state.message = text
return .none
}
}
func fetchMessage() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "loaded"
}
의미: 버튼 탭 이후 상태가 실제로 바뀌고 화면이 다시 그려질 때까지의 실행 순서
이 샘플은 외부 실 API 대신 학습용 비동기 환경 함수를 사용한다.
CounterAction.loadMessageTappedCounterAction.messageResponse(String)CounterAction.messageFailed(String)CounterAction.cancelLoadTapped + cancel IDQ. "데이터 흐름(실행 순서 요약)"은 무슨 뜻?
A. 버튼 탭 이후 Action 전달, Reducer 처리, Effect 재전달, 렌더까지 실제 실행 순서를 짧게 묶어 표현한 것.
Q. 왜 취소 ID가 필요한가?
A. 동일 목적의 오래된 비동기 작업을 취소하고 최신 요청만 유지하기 위해서다.
Q. View가 상태를 직접 바꿔도 되나?
A. 권장하지 않는다. TCA 원칙은 Action을 통해서만 상태가 변경된다.
Q. 지금 코드는 진짜 TCA 라이브러리인가?
A. 아니고 학습용 Mini TCA다. 개념은 동일하고 실제 라이브러리 문법은 더 강력하다.