ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCA 1.0 - TCA Binding (ch.04)
    TCA 2024. 2. 12. 09:32

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 TCA의 기본개념들을 넘어 또 중요한 파트인 TCA Binding에 대해 알아보겠습니다 🙋🏻

     

    항상 포스팅에서도 소개했지만, TCA 1.0 시리즈 학습은 아래 학습자료를 기반으로 하고 있습니다.

    해당 레퍼를 기반으로 학습하면서 제 나름대로 정리해보는 포스팅이기에, 주관적인 사견이 추가됩니다 🙋🏻

     

     

    Chapter 4. TCA Binding | Notion

    4.1 SwiftUI Binding VS TCA Binding

    axiomatic-fuschia-666.notion.site

     

    그럼 바로 알아보시죠 🚀


    TCA Binding


    SwiftUI Binding vs TCA Binding

    • SwiftUI에선 @State, @Binding, @ObservedObject들을 통해 양방향 바인딩 구현 및 상태관리를 함
    • SWiftUI에서 상태 관리가 복잡해질수록 사이드 이펙트 관리에 어려움을 겪을 수 있음
    • 그렇기에 TCA의 Binding은 State 관리 바인딩 도구들을 제공하여 단방향 데이터 흐름 원칙을 지킴
    • 그로 인해, 사이드 이펙트 관리 및 복잡한 상태들을 일관되게 처리할 수 있음
    • 간단한 뷰하고 로직을 짜는데 여러 코드가 들어가야하는것은 TCA를 사용한다면 어쩔수 없긴하다고 생각함

    TCA Binding

    • 가장 기본적으로 Binding(get:send:)의 형태로 TCA는 바인딩을 함
      • get: State를 바인딩 값으로 변환
      • send: 바인딩 값을 다시 Store에 피드백하는 Action으로 변환
    • ReducerProtocol 이전에도 늘 사용하던 방식이라 익숙한 방식
    // Core
    struct Main: Reducer {
      struct State: Equatable {
        var isOnAlarm: Bool = false
      }
      
      enum Action {
        case isOnAlarmChanged(Bool)
      }
      
      func reduce(into state: inout State, action: Action) -> Effect<Action> {
        switch action {
        case let .isOnAlarmChanged(isOn):
          state.isOnAlarm = isOn
          return .none
        }
      }
    }
    
    // View
    struct MainView: View {
      let store: StoreOf<Main>
      
      var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in 
          Toggle(
            "Alarm",
            isOn: viewStore.binding(
              get: \.isOnAlarm,
              send: { .isOnAlarmChanged($0) }
            )
          )
        }
      }
    }
    • 이러한 형태로 구성이 됨
    • Toggle 컴포넌트의 isOn 바인딩 파라미터에 바인딩 객체를 전달하는 형식
    • 여기서 get을 통해서 SwiftUI 컴포넌트와 바인딩 통신을 하고 send를 통해서 Store 내부에 바인딩 값을 전달하여 상태 변경과 같은 비지니스 로직을 수행하게 됨
    • 기존에는 이렇게만 썼었는데, 이것의 가장 큰 문제점이라기 보다는 귀찮은 점은 이런 바인딩되는 State가 많다면 코어에서 State, Action, Reducer에 모두 코드가 구현되어야함
    • 즉, 반복 작업을 동반하게되고 리듀서가 점점 커져 관리가 복잡해질 수 있다는 점이 단점이라면 단점 🥹

    다양한 TCA의 Binding tools

    • 위 알아봤던 바인딩의 문제들을 해결하고자 TCA에선 다양한 바인딩 도구들을 제공하게됨 
    • BindingState, BindingAction, BindingReducer들이 이제 적극적으로 활용되는데, 이전에 안다뤄봤던 영역이라 자세히 알아볼 예정!

    BindingState

    struct Main: Reducer {
      struct State: Equatable {
        @BindingState var isOnAlarm = false
      }
    }
    • 이와 같이 @BindingState 프로퍼티 래퍼를 붙여 해당 필드 값을 뷰의 UI 컴포넌트에서 바인딩 가능하도록 만들어줌
    • 그럼 이제, 해당 값은 바인딩 값이 되었기에 바로 뷰에서 해당 필드 값의 조정이 가능한것!
    • 다만, 외부에서 직접 변경이 당연히 가능한 형태이니 캡슐화가 잘 지켜지지 않기에 꼭 SwiftUI 기본 컴포넌트에 전달하기 위한 필드들에만 사용하는것을 권장
    • 근데 이제 최신 버전에서는 매크로 등장으로 BindingState 프로퍼티 래퍼 안붙여도됨
    @ObservableState
    struct State {
      var isOnAlarm = true
    }
    • 이렇게 변경된걸로 쓰게 되면 뷰에서도 @Bindable var store: StoreOf<Feature> 처럼 사용

    BindingAction

    • Action enum에 BindableAction 프로토콜을 채택하여, State의 모든 필드 액션을 하나의 케이스로 병합함으로 깔끔하게 제공해줌
    • 제네릭 타입을 갖는 BindingAction을 인자로 받음 -> BindingAction<State>
    enum Action: BindableAction {
      case binding(BindingAction<State>)
    }

     


    BindingReducer

    • Binding Action이 들어올 때, State 업데이트 쳐주는 리듀서!
    var body: some Reducer<State, Action> {
      BindingReducer()
    }
    • 이것도 본 리듀서 전에 써줘야하는듯...!?
    • 아래와 같이 구성한다면 이제 뷰에서 더 쉽고 직관적이게 코드를 가져갈 수 있음
    Toggle(
      "Alarm is On",
      isOn: viewStore.$isOnAlarm
    )
    • 아주아주 편하긴 함!!
    • BindingReducer가 body 내부에서 작동하고 바인딩 액션이 수신되면 상태를 변경
    • BindingReducer는 State와 Action 사이를 바인딩하는 역할을 가짐
    • 즉, 바인딩 State 요소가 업데이트되면 BindingReducer가 해당 상태값과 액션을 수신하여 Reducer 클로저 내에서 도메인 로직을 처리해 결과를 State에 반영
    • 추가로, 바인딩 시 더 다른 로직을 추가하고 싶다면 아래와 같이 binding 액션 케이스에서 구현해줄 수 있으 
    var body: some Reducer<State, Action> {
      BindingReducer()
      
      Reduce { state, action in 
        switch action { 
        case .binding(\.isOnAlarm):
          // 다른 작업 로직!
          
        case .binding(_): break
        }
      }
    }

     


    Binding(get:send:) vs TCA Binding tools

    • 두 바인딩 방식의 어떤 차이가 있을까?
    • BindingState를 사용하면 뷰에서는 원래 익숙한 바닐라 SwiftUI 방식처럼 객체 바인딩을 할 수 있어서 편리함
    • SwiftUI의 바인딩은 양방향으로 BindingState는 데이터 바인딩을 하며 내부 로직 자체는 단방향으로 동작
    • BindingAction을 사용함으로 각 바인딩이 필요한 State 프로퍼티별로 액션을 기존처럼 전부 일일히 하나씩 수동으로 작업하지 않아도됨!
    • 하나의 바인딩 케이스로 대체되는데 이게 너무너무 편해졌다 👍
    • 이제는 단순 필드 값 바인딩을 위한 액션이 불필요하다~
    • 또한 리듀서에서도 해당 액션을 그냥 생략해버려도됨
    • BindingReducer도 BindingAction 프로토콜을 채택한 액션을 사용함으로써 훨씬 리듀서 자체도 라이트해짐
    • 그냥 너무 편해진것 같은데..? 이것도 최신 버전에서는 또 다른 변화가 있나..?
    • 최신 버전에서는 더 줄여서 표현되고 있으니.. 대체 어디까지 줄어들까.. 이러다 뷰만 남겠는데 다시 🤔

    View State Binding

    • 각 뷰들은 자신만의 View State를 가질 수 있음 (챕터 3의 내용)
    • 스토어 외부에 있는 View State 바인딩을 위해선 ViewState 필드엔 @BindingViewState를 사용해야함
    struct MainView: View {
      let store: StoreOf<Main>
      
      struct ViewState: Equatable {
        @BindingViewState var sendNotification: Bool
      }
      
      var body: some View {
        WithViewStore(
          self.store,
          observe: { bindingViewStore in 
            ViewState(
              sendNotification: bindingViewStore.$sendNotification
            )
          }
        )
      }
    }
    • 이렇게 사용된다고 하는데 음.. 왜 쓰지?
    • 코어 단에서 하위 리듀서를 풀백 받아서 처리하는것으로도 될것 같은데 어떤 이점이 있을까!?
    • TCACoordinator 라이브러리를 같이 쓴다면 뭔가 더더욱 쓸일이 없을것 같은데.. 아직 잘 모르겠다~ 🥲
    • 아래와 같이 ViewState 이니셜라이저로 해놓는다면 더 편하게 사용할 수 있음
    struct MainView: View {
      struct ViewState: Equatable {
        @BindingViewState var sendNotification: Bool
        
        init(bindingViewStore: BindingViewStore<Notification.State>) {
          self._sendNotification = bindingViewStore.$sendNotification
        }
      }
      
      var body: some View {
        WithViewStore(self.store, observe: ViewState.init) { viewStore in
          Toggle(
          "Send Notification",
          isOn: viewStore.$sendNotification
          )
        }
      }
    }
    • 스토어 외부에 있는 ViewState와 바인딩을 할 때 사용된다는건 알겠다!

    소감

    • 아직 명확히 이해가 안되는 부분들도 있지만, 확실하게 Binding이 너무너무 편해졌다 😀
    • 기존에는 binding(get:set:)으로 귀찮고 누락되거나 헷갈려서 고통받는 나날들이 이제는 안녕

    레퍼런스 

     

    Chapter 4. TCA Binding | Notion

    4.1 SwiftUI Binding VS TCA Binding

    axiomatic-fuschia-666.notion.site

Designed by Tistory.