[TCA] Stack-based navigation のStoreはメモリリークする

### わかったこと

- Stack-based navigationを使った画面遷移では子画面から親画面へpopしても子Storeが残ったまま、消えない
- 大きなデータ/大量データを取り扱う場合、現状、Stack-based navigationは採用しない方が良さそう。採用する場合は、無視できるサイズの値しかStateには保持しない、といった設計が必要

※間違い/回避方法をご指摘いただけると助かります🙇

### 関連してそうなTCAのissue
- [StackState/StackReducer related leak](https://github.com/pointfreeco/swift-composable-architecture/issues/2508) 
- [Store scoping issue with ForEach](https://github.com/pointfreeco/swift-composable-architecture/issues/2846)
**→ このissueと原因が同じならリークを避けたい場合はTCA1.5未満にするかTCA2.0を待つかということになる**

### 検証環境

Xcode 15.4 / TCA 1.11.2 / iOS 17.5

### 検証結果

- Stateに格納したオブジェクトは `StackState` からそのStateを削除しても deinit は呼ばれない
   - Debug Memory GraphからもStoreが残っていることを確認できる。push/popの繰り返しでStoreが溜まっていく
![xxx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/e6f00557-7a45-39b9-365b-9459412c3f18.png)

- Tree-based navigationの場合も子画面から戻った後、子Storeは残ったまま。ただし、再度、子画面を表示するタイミング(Stateに子Stateを設定するタイミングで)、前回のStateのオブジェクトは解放される。

### 検証コード

```swift
import ComposableArchitecture
import SwiftUI

@Reducer
struct StateLeakDemo {
  @ObservableState
  struct State: Equatable {
    // for Stack-based navigation
    var path = StackState<Path.State>()
    // for Tree-based navigation
    @Presents var destination: Destination.State?
  }

  enum Action {
    // for Stack-based navigation
    case path(StackActionOf<Path>)
    // for Tree-based navigation
    case destination(PresentationAction<Destination.Action>)
    
    case tapStackBasedNavigation
    case tapTreeBasedNavigation
  }

  // for Stack-based navigation
  @Reducer(state: .equatable)
  enum Path {
    case screen1(Screen1)
    case screen2(Screen2)
  }
  
  // for Tree-based navigation
  @Reducer(state: .equatable)
  enum Destination {
    case screen3(Screen3)
  }
  
  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case .tapStackBasedNavigation:
        state.path.append(.screen1(.init()))
        return .none
        
      case let .path(action):
        switch action {
        case .element(id: _, action: .screen1(.tapScreen2Button)):
          state.path.append(.screen2(.init()))
          return .none

        case .element(id: _, action: .screen1(.tapBackButton)),
            .element(id: _, action: .screen2(.tapBackButton)):
          state.path.removeLast()
          return .none
         
        default:
          return .none
        }
        
      case .tapTreeBasedNavigation:
        state.destination = .screen3(.init())
        return .none
        
      case .destination(.presented(.screen3(.tapBackButton))):
        state.destination = nil
        return .none
        
      case .destination:
        return .none
      }
    }
    .forEach(\.path, action: \.path)
    .ifLet(\.$destination, action: \.destination)
  }
}

struct StateLeakDemoView: View {
  @Bindable var store: StoreOf<StateLeakDemo>

  var body: some View {
    NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
      Form {
        Section {
          Button(action: { store.send(.tapStackBasedNavigation )}, label: { Text("Stack-based navigation") })
          Button(action: { store.send(.tapTreeBasedNavigation )}, label: { Text("Tree-based navigation") })
        }
      }
      .navigationTitle("Root")
      .navigationDestination(
        item: $store.scope(state: \.destination?.screen3, action: \.destination.screen3)
      ) { store in
        Screen3View(store: store)
      }
    } destination: { store in
      switch store.case {
      case let .screen1(store):
        Screen1View(store: store)
      case let .screen2(store):
        Screen2View(store: store)
      }
    }
  }
}

// MARK: - subscreen 1 (Stack-based用)

@Reducer
struct Screen1 {
  @ObservableState
  struct State: Equatable {
    var hoge: Hoge = .init()
  }
  
  enum Action {
    case tapBackButton
    case tapScreen2Button
  }
  
  var body: some Reducer<State, Action> {
    Reduce { _, _ in
      return .none
    }
  }
}

struct Screen1View: View {
  let store: StoreOf<Screen1>
  
  var body: some View {
    Form {
      Button("Back") {
        store.send(.tapBackButton)
      }
      Button("Screen 2") {
        store.send(.tapScreen2Button)
      }
    }
    .navigationTitle("Screen 1")
  }
}

// MARK: - subscreen 2 (Stack-based用)

@Reducer
struct Screen2 {
  @ObservableState
  struct State: Equatable {
    var hoge: Hoge = .init()
  }
  
  enum Action {
    case tapBackButton
  }
  
  var body: some Reducer<State, Action> {
    Reduce { _, _ in
      return .none
    }
  }
}

struct Screen2View: View {
  let store: StoreOf<Screen2>

  var body: some View {
    Form {
      Button("Back") {
        store.send(.tapBackButton)
      }
    }
    .navigationTitle("Screen 2")
  }
}

// MARK: - subscreen 3 (Tree-based用)

@Reducer
struct Screen3 {
  @ObservableState
  struct State: Equatable {
    var hoge: Hoge = .init()
  }
  
  enum Action {
    case tapBackButton
  }
  
  var body: some Reducer<State, Action> {
    Reduce { _, _ in
      return .none
    }
  }
}

struct Screen3View: View {
  let store: StoreOf<Screen3>
  
  var body: some View {
    Form {
      Button("Back") {
        store.send(.tapBackButton)
      }
    }
    .navigationTitle("Screen 3")
  }
}

// MARK: - Stateに保持するオブジェクト

class Hoge: Equatable {
  init() {
    self.uuid = UUID().uuidString
    print("Hoge init uuid[\(self.uuid)]")
  }
  
  deinit {
    print("Hoge deinit uuid[\(self.uuid)]")
  }
  
  private var uuid: String
  
  static func == (lhs: Hoge, rhs: Hoge) -> Bool {
    lhs.uuid == rhs.uuid
  }
}

```