IOSアプリケーションの画面間でデータをナビゲートおよび転送するときにEnum +関連する値を使用する

この投稿では、IOSアプリケーションの画面間のナビゲーションとデータ転送を整理するという古くからの質問に触れたいと思います。まず、私のアプローチの概念を伝えたいと思います。それを魔法の薬として使用するように説得するのではありません。ここでは、さまざまなアーキテクチャのアプローチや、セグでUlStoryboardを使用する可能性については検討しません。一般に、長所と短所で目的を達成するための別の可能な方法について説明します。それでは、始めましょう!



背景:



もちろん、アーキテクチャアプローチの選択は、プロジェクトでのナビゲーションの実装とデータ転送の編成に影響しますが、アプローチ自体は、チームの構成、市場投入までの時間、技術仕様の状態、プロジェクトのスケーラビリティなど、さまざまな状況で構成されます。



  • MVVMの必須使用。
  • 新しい画面(コントローラーとそのビューモデル)をナビゲーションプロセスにすばやく追加する機能。
  • ビジネスロジックの変更はナビゲーションに影響を与えないはずです。
  • ナビゲーションの変更はビジネスロジックに影響を与えるべきではありません。
  • ナビゲーションを修正せずに画面をすばやく再利用する機能。
  • 既存の画面のアイデアをすばやく取得する機能;
  • プロジェクト内の依存関係をすばやく把握する機能。
  • 開発者がプロ​​ジェクトに参加するためのしきい値を上げないでください。




要点をつかむ



最終的な解決策は1日で形成されなかったこと、欠点がないわけではなく、中小規模のプロジェクトにより適していることに注意してください。わかりやすくするために、テストプロジェクトは次の場所で表示できます:github.com/ArturRuZ/NavigationDemo



1.既存の画面をすばやく把握できるようにするために、明確な名前ControllersListで列挙を作成することにしました。



enum ControllersList {
   case textInputScreen
   case textConfirmationScreen
}


2.いくつかの理由で、プロジェクトはDIにサードパーティのソリューションを使用したくありませんでした。また、プロジェクトの依存関係をすばやく表示する機能を含め、DIを取得したかったので、個別の画面(アセンブリプロトコルで閉じられます)ごとにAssemblyを使用し、一般的な範囲。




protocol Assembly {
   func build() -> UIViewController
}

final class TextInputAssembly: Assembly {
   func build() -> UIViewController {
      let viewModel = TextInputViewModel()
      return TextInputViewController(viewModel: viewModel)
   }
}

final class TextConfirmationAssembly: Assembly {
   private let text: String
   
   init(text: String) {
      self.text = text
   }
   
   func build() -> UIViewController {
      let viewModel = TextConfirmationViewModel(text: text)
      return TextConfirmationViewController(viewModel: viewModel)
   }
}


3.画面間でデータを転送するには(実際に必要な場合)、ControllersListは関連する値を持つ列挙型になります。



enum ControllersList {
   case textInputScreen
   case textConfirmationScreen(text: String)
}


4.ビジネスロジックがナビゲーションやビジネスロジックのナビゲーションに影響を与えないようにするため、および画面をすばやく再利用するために、ナビゲーションを別のレイヤーに移動する必要がありました。これは、コーディネーターとコーディネーションプロトコルがどのように登場したかです。




protocol Coordination {
   func show(view: ControllersList, firstPosition: Bool)
   func popFromCurrentController()
}

final class Coordinator {
   
   private var navigationController = UINavigationController()
   private var factory: ControllerBuilder?
   
   private func navigateWithFirstPositionInStack(to: UIViewController) {
      navigationController.viewControllers = [to]
   }
   private func navigate(to: UIViewController) {
      navigationController.pushViewController(to, animated: true)
   }
}

extension Coordinator: Coordination {
   func popFromCurrentController() {
      navigationController.popViewController(animated: true)
   }
   func show(view: ControllersList, firstPosition: Bool) {
      guard let controller = factory?.buildController(for: view) else { return }
                 firstPosition ?  navigateWithFirstPositionInStack(to: controller) : navigate(to: controller)
   }
}



ここで重要なのは、プロトコルがより多くのメソッドを記述できることです。コーディネーターと同様に、ニーズに応じてさまざまなプロトコルを実装できます。



5.これらすべてを踏まえて、アプリケーションに新しい画面を追加することにより、開発者が実行する必要のある一連のアクションも制限したいと思いました。現時点では、どこかで依存関係を登録する必要があり、ナビゲーションを機能させるために他のアクションを実行できることを覚えておく必要がありました。



6.追加のルーターやコーディネーターを作成したくありませんでした。さらに、ナビゲーション用の追加のロジックを作成すると、ナビゲーションの認識と画面の再利用の両方が大幅に複雑になる可能性があります。これらすべてが、最終的に次のような一連の変更につながりました。




//MARK - Dependences with controllers associations
fileprivate extension ControllersList {
   typealias scope = AssemblyServices
  
   var assembly: Assembly {
      switch self {
      case .textInputScreen:
         return TextInputAssembly(coordinator: scope.coordinator)
      case .textConfirmationScreen(let text):
         return TextConfirmationAssembly(coordinator: scope.coordinator, text: text)
      }
   }
}

//MARK - Services all time in memory
fileprivate enum AssemblyServices {
   static let coordinator: oordinationDependencesRegstration = Coordinator()
   static let controllerFactory: ControllerBuilderDependencesRegistration = ControllerFacotry()
}

//MARL: - RootAssembly Implementation
final class  RootAssembly {
   fileprivate typealias scope = AssemblyServices
  
   private func registerPropertyDependences() {
//     this place for propery dependences
   }
}


// MARK: - AssemblyDataSource implementation
extension RootAssembly: AssemblyDataSource {
   func getAssembly(key: ControllersList) -> Assembly? {
      return key.assembly
   }
}


これで、新しい画面を作成するときに、開発者はControllersListに変更を加えるだけで済み、コンパイラ自体が変更を加える必要がある場所を示しました。ControllersListに新しい画面を追加しても、現在のナビゲーションスキームにはまったく影響がなく、依存関係の管理ロジックを簡単にたどることができました。また、ControllersListを使用すると、特定の画面へのすべてのエントリポイントを簡単に見つけることができ、画面の再利用が容易になりました。



結論



この例は、アイデアの単純化された実装であり、すべてのユースケースを網羅しているわけではありません。それでも、アプローチ自体は非常に柔軟で適応性があることが証明されました。



このアプローチの欠点は次のとおりです。



  • , , . ControllersList NavigationEvents, , ;
  • , ;
  • , , . , .


IOSアプリケーションでのナビゲーションとデータ転送に関する投稿のほとんどは、コーディネーターとルーター(画面のそれぞれまたはグループ)の使用、またはセグ、シングルトンなどを介したナビゲーションに影響しますが、これらのオプションはどれも私には適していませんでした理由。



おそらく、このアプローチは問題を解決するのに適しています。お時間をいただきありがとうございます。



All Articles