[iOS] TCA

Judy·2023년 7월 21일
0

iOS

목록 보기
26/28

TCA란

The Composable Architecture
Elm이나 Redux 라이브러리의 아이디어에 기반하여 Point-Free에서 만든 상태 관리 기반 단방향 아키텍처

  • 목적: 일관되고 이해할 수 있는 방식으로 어플리케이션을 만들기 위해
  • 목표: Composition(합성) Testing(테스팅) Ergonomics(인체 공학)
  • 지원: SwiftUI, UIKit 및 모든 애플 플랫폼

최소 개발 타겟

Combine 프레임워크를 dependency로 가지고 있어 최소 iOS 13, macOS 10.15, Mac Catalyst 12, tvOS 12 그리고 watchOS 6 이상이어야 함
(만약 더 이전 OS를 지원해야 하는 경우 ReactiveSwift나 RxSwift 버전을 사용)

지원 내용

1) State

값 타입으로 앱 상태를 관리하고 상태 공유를 통해 화면에서 일어나는 변화를 다른 화면에서 즉시 관측하는 방법 제공

2) Composition

기능을 여러 독립된 모듈로 추출하거나 다시 결합하여 컴포넌트의 집합으로 구성하는 방법 제공

3) Side Effects

테스트 가능하고 이해하기 쉬운 방식으로 외부와 통신하도록 하는 방법 제공

4) Testing

아키텍처 내부의 기능 테스트 뿐 아니라 여러 파트로 구성된 기능의 통합 테스트 작성
사이트 이펙트가 앱에 끼치는 영향을 테스트
이러한 테스트를 통해 비즈니스 로직이 잘 작동하는지 보장

5) Ergonomics

위 내용들을 가능한 적은 개념의 간단한 API로 이루는 방법 제공

TCA 적용 방법

1) Composable Architecture 의존성 추가

  • SPM으로 추가 (현재는 SPM으로만 지원하고 있는 것으로 확인됨)
  • File 메뉴에서 Swift Packages › Add Package Dependency를 선택
    https://github.com/pointfreeco/swift-composable-architecture 를 입력하여 추가

2) TCA를 통해 기능을 만들기 위해 도메인을 구성하는 타입을 정의

State
비즈니스 로직 수행 및 UI 렌더링을 위한 데이터 타입

Action
사용자 액션, 알림 및 이벤트 등 앱에서 발생할 수 있는 모든 작업을 나타내는 타입

Reducer
액션(Action)에 대해 상태(State)를 다음 상태로 변화시키는 함수. EffectTask를 반환하 여 실행할 수 있는 이펙트(Effect, API 요청 등)를 반환하는 역할

Store
실제 기능을 구동하는 공간. 사용자 행동(Action)을 Store로 보내면 Reducer와 Effect 를 실행할 수 있고, Store에서 일어나는 상태(State) 변화를 관측하여 UI를 업데이트

타입 구현 시 장점

  • 기능에 즉시 테스트 가능성 부여
  • 크고 복잡한 기능을 결합 가능한 작고 독립된 모듈로 쪼갤 수 있음

구현 예시

1) ReducerProtocol을 준수하여 기능의 도메인과 동작을 수용할 타입을 생성

struct Feature: ReducerProtocol {  

}

2) 기능의 State 타입 정의

struct Feature: ReducerProtocol {  
	struct State: Equatable {    
		var count = 0    
		var numberFactAlert: String?  
	}
}

3) 기능에 대한 Action 타입 정의

struct Feature: ReducerProtocol {  
 enum Action: Equatable {  
		case factAlertDismissed    
		case decrementButtonTapped    
		case incrementButtonTapped    
		case numberFactButtonTapped    
		case numberFactResponse(String)  
	}
}

4) 기능에 대한 실제 논리 및 동작을 처리하는 reduce(into:action) 메서드 구현

struct Feature: ReducerProtocol { 
	func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
		switch action { 
			case .factAlertDismissed: 
				state.numberFactAlert = nil 
				return .none 
			case .decrementButtonTapped: 
				state.count -= 1 
				return .none 
			case .incrementButtonTapped: 
				state.count += 1 
				return .none 
			case .numberFactButtonTapped: 	
				return .run { [count = state.count] send in 
					let (data, _) = try await URLSession.shared.data( 
						from: URL(string: "http://numbersapi.com/\(count)/trivia")! 
					) 
					await send( 
						.numberFactResponse(String(decoding: data, as: UTF8.self) 
					) 
				} 
			case let .numberFactResponse(fact): 
				state.numberFactAlert = fact 
				return .none 
			}
		} 
	} 
}
  • 현재 State를 다음 State로 변경하는 방법 설명
  • 어떤 Effect를 실행해야 하는지 설명
  • 실행할 Effect가 없는 작업은 .none을 반환

6) View 정의

struct FeatureView: View {  
	let store: StoreOf<Feature>
	
	var body: some View {    
	  WithViewStore(self.store, observe: { $0 }) { viewStore in      
		VStack {        
		  HStack {          
			Button("−") { viewStore.send(.decrementButtonTapped) }          
			Text("\(viewStore.count)")         
 			Button("+") { viewStore.send(.incrementButtonTapped) }        
		}
        
		Button("Number fact") { viewStore.send(.numberFactButtonTapped) }  
	  }
	  .alert(        
	    item: viewStore.binding(          
	  	  get: { $0.numberFactAlert.map(FactAlert.init(title:)) },          
		  send: .factAlertDismissed        
		),        
	    content: { Alert(title: Text($0.title)) }      
	  )     
	}  
  }
}
  • StoreOf<Feature>을 유지
  • Feature은 ReducerProtocol을 준수한 타입
  • State의 모든 변경 사항을 관찰하고 다시 렌더링하고 State 변경을 위해 ActionStore에게 전달(send)

7) Store를 가진 View 생성

@main
struct MyApp: App {  
	var body: some Scene {    
		FeatureView(      
			store: Store(initialState: Feature.State()) {        
				Feature()      
			}    
		)  
	}
}

초기 State와 앱을 가동시킬 Reducer를 지정


참고 자료
개발 문서

profile
iOS Developer

1개의 댓글

comment-user-thumbnail
2023년 7월 21일

항상 좋은 글 감사합니다.

답글 달기