블로그를 작성한지 1주일이 되었습니다... 사정이 있어 글이 늦었습니다.
이번 시간에는 TCA 최신 버전인 1.7 버전을 다루어 보려고 합니다.
Point-Free에서 개발한 거의 프레임워크에 가까운 라이브러리입니다.
각 View, State, Action, Reducer, Store 로 구성된 구조인데
혹시 전편인 Redux 패턴을 보고 오셨다면 꽤 익숙한 구조일것 같습니다.
TCA에서도 Redux패턴에 대한 내용이 있으니 해당 부분에대해 더 관심이 있다면 참고 해주세요.
한번 꼭 보고 와주시길 바랍니다.
< 참고 링크 > TCA 문서 링크
< 전편 Redux패턴 > Redux 패턴에 대해서
State: 사용자와 상호 작용할 데이터의 집합이며 이는 특정 기능이나 컴포넌트의 전체 상태를 말합니다.
Action: 사용자의 이벤트나 기능에서 발생할 수 있는 모든 이벤트 즉 행동을 나타내는 열거형 입니다.
Dependency: 사이드 이펙트를 동반하는 모든 의존성 및 서비스를 담당하는 구조체 혹은 객체 입니다.
Reducer: 현재 상태와 액션을 받아 새로운 상태를 반환하는 함수이며
Action이 주어지면 앱의 현재 상태를 다음 State로 업데이트 하는 함수입니다.
Store: 모든 사용자 작업을 Store로 전송하여 Store에서 Reducer와 Effect를 실행하며 State변화를 관찰하여 UI를 업데이트 합니다.
연습코드(프로젝트 링크 에서 작성했던 코드입니다.
import ComposableArchitecture
import Foundation
@Reducer // 메크로 Reducer
struct CounterFeature {
@ObservableState // TCA 의 관찰 옵저버블
struct State: Equatable { // 상태 관리 구조체
var count = 0
var fact: String?
var isLoading = false
var isTimerRunning = false
}
enum Action { // 사용자의 액션
case decrementButtonTapped // 감소 버튼 탭
case incrementButtonTapped // 증가 버튼 탭
case factButtonTapped
case factResponse(String)
case toggleTimerButtonTapped
case timerTick
}
enum CancelID { case timer } // TCA Cancelable ID 를 제공하여 효과를 쥐소할수 있다.
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
case .incrementButtonTapped:
state.count += 1
return .none
case .factButtonTapped:
state.fact = nil
state.isLoading = true
return .run { [ count = state.count ] send in
print("number: ", count )
let (data, response) = try await URLSession.shared.data(from: URL(string: "http://numbersapi.com/\(count)")!)
print("SSSS",data)
let fact = String(decoding: data, as: UTF8.self)
await send(.factResponse(fact))
}
case let .factResponse(fact):
state.fact = fact
state.isLoading = false
return .none
case .toggleTimerButtonTapped:
state.isTimerRunning.toggle()
if state.isTimerRunning {
return .run { send in
while true {
try await Task.sleep(for: .seconds(1))
await send(.timerTick)
}
}
.cancellable(id: CancelID.timer)
} else {
return .cancel(id: CancelID.timer)
}
case .timerTick:
state.count += 1
state.fact = nil
return .none
}
}
}
}
예제코드를 먼저 보여드렸는데 어떠신가요?
생각보다 많이 어려울수 있을것 같습니다.
차근 차근 다시 알아가 보죠
예전 버전을 사용하셨던 분들이라면 꽤 흥미로울수 있는 매크로 일것 같습니다.
예전 같은경우엔 해당 구조체에Reducer
프로토콜을 채택하고 구현하여야 했었는데
최신 (현 1.7) 버전에선 이를 매크로를 만들어 사용할수 있게 하였습니다.
정확히는 개발자가 생략한 Reducer 프로토콜의 요구 사항을 자동으로 채워줍니다.
@Reducer
struct Feature {}
/// 예전 TCA 와 비교
struct Feature: Reducer {
... 모두 명시 꼭..
}
@Reducer
는 빈 State 구조체와, Action, body 를 자동으로 생성합니다.
이는 SwiftUI의 StateObject와 매우 유사합니다.
상태 관리와 상태 변화의 관찰을 지원하는 속성 래퍼입니다.
typealias ReducerOf<R> = Reducer<R.State, R.Action> where R : Reducer
/// 과거 방식
var body: some Reducer<State, Action> {
// ...
}
/// 현재방식
var body: some ReducerOf<Self> {
// ...
}
자 다시 위에 개념을 이해해 보고 예재코드를 살펴 보겠습니다.
컴포턴트의 상태를 나타내는 State 즉 UI를 반영할 데이터들이 담겨 있습니다.열겨형으로 사용자의 액션을 정한후 body(Reducer Method) 를 통해 Feature 의 State 를 반영하는 구조인 것이죠.
자 TCA 맛보기 편이였는데 흥미로우실까 궁금합니다.
사실 TCA는 해당하는 예재 코드가 60퍼센트 다 표현되었다고 생각합니다.
후에는 Dependency, BindingReducer(), Scope, ifLetScope,
중복 코드는 body에서 어떻게 분리하는가 정도 되어서 이번편은 이렇게 마무리 지으려고 합니다.설명이 많이 생략되어서 살짝 고민이였는데 Redux 패턴 설명때 했던 내용들이 많이 겹쳐서 이렇게 정리하게
되었습니다... 모두 고생하셨습니당
흥미로워요~~~