-
[TCA] TCA 작동 방식iOS/TCA 2023. 8. 31. 15:04
✅ TCA 작동방식에 대해 정리하고 가자!
TCA 구조의 특징
로직이 한 방향으로 흐르기 때문에 각 타입의 역할을 이해하면 side-effect 관리를 쉽게 할 수 있다
역할
State
상태값을 저장하는 객체
Action
사용자의 동작이 일어났을 때 참조하는 객체
Store
State, Action, Reducer가 모두 저장되어 있는 Observable 오브젝트
ex) 사용자가 버튼을 클릭하면 -> action에서 reducer로 전달 -> state값 변경
Reducer
들어오는 action을 필터링해서 state 값을 변경할 수 있는 함수
PrimeTime 코드 예시
State
struct AppState { var count = 0 var favoritePrimes: [Int] = [] var loggedInUser: User? var activityFeed: [Activity] = [] var didChange = PassthroughSubject<Void, Never>() struct Activity { let timestamp: Date let type: ActivityType enum ActivityType { case addedFavoritePrime(Int) case removedFavoritePrime(Int) } } struct User { let id: Int let name: String let bio: String } }
Action
public enum CounterAction { case decrementTapped case incrementTapped } public func counterReducer(state: inout Int, action: CounterAction) -> Void { switch action { case .decrementTapped: state -= 1 case .incrementTapped: state += 1 } } // ======================================== enum AppAction { case counter(CounterAction) case primeModal(PrimeModalAction) case favoritePrimes(FavoritePrimesAction) // enum properties // 프로퍼티를 사용하는 경우 AppAction 열거형에 있는 모든 케이스의 관련 데이터에 대한 인스턴스 액세스 가능 var counter: CounterAction? { get { guard case let .counter(value) = self else { return nil } return value } set { guard case .counter = self, let newValue = newValue else { return } self = .counter(newValue) } } var primeModal: PrimeModalAction? { get { guard case let .primeModal(value) = self else { return nil } return value } set { guard case .primeModal = self, let newValue = newValue else { return } self = .primeModal(newValue) } } var favoritePrimes: FavoritePrimesAction? { get { guard case let .favoritePrimes(value) = self else { return nil } return value } set { guard case .favoritePrimes = self, let newValue = newValue else { return } self = .favoritePrimes(newValue) } } }
Reducer
let _appReducer: (inout AppState, AppAction) -> Void = combine( pullback(counterReducer, value: \.count, action: \.counter), pullback(primeModalReducer, value: \.primeModal, action: \.primeModal), pullback(favoritePrimesReducer, value: \.favoritePrimes, action: \.favoritePrimes) ) let appReducer = pullback(_appReducer, value: \.self, action: \.self)
Store
public final class Store<Value, Action>: ObservableObject { private let reducer: (inout Value, Action) -> Void // store의 값을 얻는 방법: 프로퍼티를 통해서! // value값이 private setter로 되어있기 때문에 모듈 밖에서는 값을 가져오는 것 외에는 아무것도 할 수 없음 @Published public var value: Value private var cancellable: Cancellable? public init(initialValue: Value, reducer: @escaping (inout Value, Action) -> Void) { self.value = initialValue self.reducer = reducer } public func send(_ action: Action) { self.reducer(&self.value, action) print("Action: \(action)") print("Value:") dump(self.value) print("---") } // store에 자동으로 변환을 적용하여 appState의 값 중에서 일부만 가져오도록 하는 메서드 // ((Value) -> LocalValue) -> ((Store<Value, _>) -> Store<LocalValue, _> // ((A) -> B) -> ((Store<A, _>) -> Store<B, _>) // map: ((A) -> B) -> ((F<A>) -> F<B>) public func view<LocalValue>( _ f: @escaping (Value) -> LocalValue ) -> Store<LocalValue, Action> { let localStore = Store<LocalValue, Action>( initialValue: f(self.value), reducer: { localValue, action in self.send(action) localValue = f(self.value) } ) localStore.cancellable = self.$value.sink { [weak localStore] newValue in localStore?.value = f(newValue) } return localStore } } func transform<A, B, Action>( _ reducer: (inout A, Action) -> Void, _ f: (A) -> B ) -> (inout B, Action) -> Void { fatalError() } public func combine<Value, Action>( _ reducers: (inout Value, Action) -> Void... ) -> (inout Value, Action) -> Void { return { value, action in for reducer in reducers { reducer(&value, action) } } } /* =========== < After action pullback > =========== */ // Global action이 들어올 때 key path를 사용하여 Local Action을 추출하려고 시도 // -> 성공: reducer로 전달, 실패: 아무것도 안 함 public func pullback<GlobalValue, LocalValue, GlobalAction, LocalAction>( _ reducer: @escaping (inout LocalValue, LocalAction) -> Void, value: WritableKeyPath<GlobalValue, LocalValue>, action: WritableKeyPath<GlobalAction, LocalAction?> ) -> (inout GlobalValue, GlobalAction) -> Void { return { globalValue, globalAction in guard let localAction = globalAction[keyPath: action] else { return } reducer(&globalValue[keyPath: value], localAction) } } public func logging<Value, Action>( _ reducer: @escaping (inout Value, Action) -> Void ) -> (inout Value, Action) -> Void { return { value, action in reducer(&value, action) print("Action: \(action)") print("State:") dump(value) print("---") } }
'iOS > TCA' 카테고리의 다른 글
[SwiftUI] 상태관리(Higher-Order Reducers) (0) 2023.08.02 [SwiftUI] 상태관리(Action Pullbacks) (0) 2023.07.18 [SwiftUI] 상태관리(State Pullbacks) (0) 2023.07.07 [SwiftUI] 상태관리(redux) (0) 2023.07.06 [SwiftUI] 상태 관리 (0) 2023.07.05