문제 발견
HomeFeature에 모달/시트를 하나씩 추가하다 보니 어느 순간부터 Xcode 빌드가 120초 이상 걸리거나 “Expression was too complex to be solved in reasonable time” 에러가 발생했습니다. 원인을 추적하니 .ifLet 체이닝이 7개를 넘으면서 Swift 타입 체커가 기하급수적으로 느려지는 문제였습니다.
// ❌ 문제 패턴 — ifLet 7개 이상 체이닝
var body: some ReducerOf<Self> {
Reduce { state, action in ... }
.ifLet(\.$childDetail, action: \.childDetail) { ChildDetailFeature() }
.ifLet(\.$scheduleAdd, action: \.scheduleAdd) { ScheduleAddFeature() }
.ifLet(\.$academyRegist, action: \.academyRegist) { AcademyRegistFeature() }
.ifLet(\.$noticeboard, action: \.noticeboard) { NoticeboardFeature() }
.ifLet(\.$scheduleEdit, action: \.scheduleEdit) { ScheduleEditFeature() }
.ifLet(\.$profileEdit, action: \.profileEdit) { ProfileEditFeature() }
.ifLet(\.$childAdd, action: \.childAdd) { ChildAddFeature() }
// 여기부터 컴파일 타임아웃 발생!
.ifLet(\.$settingsSheet, action: \.settingsSheet) { SettingsFeature() }
}
Swift 타입 체커의 한계
Swift의 타입 추론 시스템은 some ReducerOf<Self> 반환 타입 계산 시 .ifLet 각각을 중첩 제네릭 타입으로 확장합니다. 체이닝이 길어질수록 중첩 깊이가 지수적으로 증가해 컴파일러가 포기합니다.
해결: CombineReducers로 레이어 분리
// ✅ 올바른 패턴 — 논리 그룹별 레이어 분리
var body: some ReducerOf<Self> {
CombineReducers {
HomeBaseReducer() // 핵심 비즈니스 로직
HomeModalReducer() // 모달 관련 .ifLet 묶음 (최대 3개)
HomeScheduleReducer() // 일정 관련 .ifLet 묶음 (최대 3개)
}
}
// HomeModalReducer.swift — 별도 파일로 분리
struct HomeModalReducer: Reducer {
typealias State = HomeFeature.State
typealias Action = HomeFeature.Action
var body: some ReducerOf<HomeFeature> {
Reduce { _, _ in .none }
.ifLet(\.$childDetail, action: \.childDetail) { ChildDetailFeature() }
.ifLet(\.$scheduleAdd, action: \.scheduleAdd) { ScheduleAddFeature() }
.ifLet(\.$profileEdit, action: \.profileEdit) { ProfileEditFeature() }
}
}
// HomeScheduleReducer.swift
struct HomeScheduleReducer: Reducer {
typealias State = HomeFeature.State
typealias Action = HomeFeature.Action
var body: some ReducerOf<HomeFeature> {
Reduce { _, _ in .none }
.ifLet(\.$academyRegist, action: \.academyRegist) { AcademyRegistFeature() }
.ifLet(\.$noticeboard, action: \.noticeboard) { NoticeboardFeature() }
.ifLet(\.$scheduleEdit, action: \.scheduleEdit) { ScheduleEditFeature() }
}
}
빌드 시간 비교
- 개선 전: clean build ~140초, ifLet 체이닝 부분에서 대부분 소모
- 개선 후: clean build ~45초, 레이어별 독립 컴파일
추가 팁: 타입 명시로 힌트 제공
// Effect 반환 타입을 명시하면 타입 추론 부담 감소
func reduce(into state: inout State, action: Action) -> Effect<Action> {
// 명시적 타입으로 컴파일러 힌트 제공
let effect: Effect<Action> = .none
return effect
}
교훈
TCA의 .ifLet은 강력하지만 Swift 타입 체커의 한계를 인식해야 합니다. 체이닝이 5개를 넘기 시작하면 미리 CombineReducers로 레이어를 분리하세요. 코드 구조도 개선되고 컴파일 속도도 향상되는 일석이조의 패턴입니다.