[SUSU] 수수앱에서 Navigation 방식을 정의하기 part 1 (TCA With Navigation)

씨마스터300·2024년 6월 9일
2

NavigationWithTCA

목록 보기
1/3
post-thumbnail

머리말

이 글은 TCA를 활용하면서 SUSU가 달려온 Navigation방식의 변천에 대해서 소개하는 글 입니다. SwiftUI특성상 Navigation을 정의하는 것이 결코 쉬운 일이 아니였습니다. 또한 SUSU팀이 도착한 종착역이 옳다고 할 수 없습니다. 그렇기에 비판적인 시각으로 바라봐주시면 감사합니다.



상위 Reducer에서 하위 Reducer의 Action을 관찰하기

말 그대로 NavigationStack을 관장하는 Reducer가 모든 View의 Action을 관찰하면서 이를 따라가는 것 입니다. 이를 코드로 표현하면 다음과 같습니다. 모든 화면전환 로직을 최 상위 NavigationStack을 갖고있는 Reducer가 갖고 있게 됩니다. 이를 Switch 분기를 통해서 Navigation을 정의할 수 있습니다.

struct FirstNavigation {
  @ObservableState
  struct State: Equatable {
    var isOnAppear = false
    var path: StackState<Path.State> = .init()
    init() {}
  }
  
  enum Action: Equatable {
    case onAppear(Bool)
    case goSecondScreen
    case goThirdScreen
    case path(StackActionOf<Path>)
  }
  
  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case let .onAppear(isAppear):
        state.isOnAppear = isAppear
        return .none
      case .goThirdScreen:
        state.path.append(.third(.init()))
        return .none
        
      case .goSecondScreen:
        state.path.append(.second(.init()))
        return .none
        
      case let .path(.element(id: _, action: action)):
        switch action {
        case .second(.goThirdScreen):
          state.path.append(.third(.init()))
          return .none
        case .third(.goSecondScreen):
          state.path.append(.second(.init()))
          return .none
        default:
          return .none
        }
      case .path:
        return .none
      }
    }
    .forEach(\.path, action: \.path)
  }
  
  @Reducer(state: .equatable, action: .equatable)
  enum Path {
    case second(SecondNavigation)
    case third(ThirdNavigation)
  }
}


동작 순서

  1. A View에서 Navigation에 관한 Action 발생
  2. TopView는 Navigation에 관한 Action을 분기처리 했으니까(body), state에 Navigation Destionation에 대한 State를 추가함
  3. NavigationStack뷰를 갖고 있는 TopView가 화면 전환

Pros

  • Swift Compiler를 통한 안전한 NavigationStack Destination 생성

    • 이 방법은 별도의 로직이 필요없이 SwiftCompiler가 TypeCheck를 해주면서 구현되지 않은 Navigation에 Error을 발생시킵니다. Swiftch Compiler를 통해 안전한 처리가 가능합니다. (만약 Path에 case 를 추가하였지만, NavigationDestination을 정의하지 않았다면 자동적으로 에러가 발생하게 됩니다.)
  • 높은 응집성

    • 관리만 잘 해준다면, 모든 화면분기 로직을 담당하는 객체가 있으니까 높은 응집성을 보입니다. (NavigationStack을 가진 객체와 Path를 가진 Reducer )



Cons

모든 Navigation 로직을 Navigation Reducer을 통해 관리합니다. 이 방법은 TCA에서 소개하는 Navigation방식중에 하나라서, 많은 곳에서 활용합니다. 이를 관리하기가 정말 힘듭니다. 왜냐하면 뷰가 많아지면 많아질수록 코드는 읽기 힘들어지고, 빌드 속도는 매우 느려집니다. 실제 이것을 프로덕트에 적용하면 다음과 같은 코드가 탄생(?)합니다.
비대한 Reducer Body

비대한 Reducer Body을 보면 알겠지만, 관리가 정말 힘듭니다. 사실 관리만 힘들면 그렇지만, 읽는게 힘들면 사실 그 코드는 더이상 건드리고 싶지 않기 때문에 이 방법 말고 다른 방법을 찾아야 했습니다.



실제 구현된 모습

view

전체 코드

누를 시 깃허브로 이동됩니다.

profile
승리하면 작은 것을 배울 수 있다. 그러나 패배하면 모든 것을 배울 수 있다.

0개의 댓글

관련 채용 정보