シルバー:iOSアプリの設計方法

別のアーキテクチャ?



近年、iOSプラットフォーム用のアプリケーションを作成するための代替アーキテクチャのトピックが注目を集めています。 MVP、MVVM、VIPERとして知られる一部の有力者は、すでに特別名誉委員会に参加しています。 そしてそれらに加えて、それほど一般的ではない他の多くがあります。







強者の間では、私の意見では、すべての場合に普遍的な錠剤はありません:









複数のアーキテクチャを使用するオプションがあります。多くのアーキテクチャでは、程度を変えることで他のアーキテクチャと組み合わせることができますが、少なくとも3つの理由であまり便利ではありません。









そして過去4年間、多くのプロジェクト(銀行セクターからのいくつかのプロジェクト、いくつかの異機種混合のカスタムプロジェクト、および自分自身のいくつかのアプリケーションとゲームの両方)に直面して、私は自分でアーキテクチャアプローチを形成しました。私が始めているプロジェクト。







これまでのところ、彼は私に失敗していません。 同時に、私は開拓者だとは思いません。確かに、多くの人がすでに同様のアプローチを使用しています。 しかし、私が個人的に遭遇したプロジェクトは建築に関してかなり難しいので、自分の考えを共有したいと思いました。







シルバーについて簡単に



このアーキテクチャバリアントの形成では、いくつかの重要な側面が考慮されました。









要約すると:









アーキテクチャの主要部分:









私は条件付きでそれをシルバーと呼びます:最初の文字。







例による銀



地理に関する独自の知識を期待して、自分たちが覚えている国と都市のリストを保持する小さなデモアプリケーションを収集します。







まず、モジュールの公開プレゼンテーションを見てみましょう。 このフレーズでは、モジュールは制御可能な集合イメージを表し、そのステータスは画面に表示できます。 したがって、どのモジュールにも2つの公開部分があります。









protocol IBaseRouter: class { var viewController: UIViewController { get } } struct Module<RT> { let router: RT let viewController: UIViewController }
      
      





これにより、 ViewControllerが構造の別のプロパティに繰り返し接続されている場合、それらを繰り返した理由が尋ねられる場合があります。







その理由は、最も単純なメモリ管理を確保するために、 ViewControllerがモジュールの残りの部分と強力に接続しているという事実に重点が移っているということです。現在の画面から戻ると、 ViewControllerはUIKit階層から削除され、モジュール全体。







同じ理由で、親モジュールから、子ルーターとの通信は、必要に応じて弱められます。







そのため、メモリを詰まらせないために、初めてViewControllerが作成されるのは、アクセスされた瞬間のみです。 したがって、実行可能なモジュールを表示するには、そのViewControllerを使用する必要があります。 ただし、制御できるようにするには、 ルーターと通信する必要があります。







モジュールファクトリからルーターを取得すると、モジュールへの強力なリンクがなくなり、次のコード行で既に破棄されます。 また、工場からViewControllerを取得した場合、モジュールを制御および構成することはできません。







この問題は、モジュールが作成されたときに満たされているモジュール構造によって解決され、 ルーターViewControllerへの両方の強力なリンクを一時的に保持することができます。 その結果、構造がローカルスコープでまだ生きている間に、 ルーターを弱いリンクに保存し、UIKitが強いリンクを保持する画面にViewControllerを表示できます







モジュール作成の例
 func InputModuleAssembly(title: String, placeholder: String, doneButton: String) -> Module<IInputRouter> { let router = InputRouter(title: title, placeholder: placeholder, doneButton: doneButton) return Module<IInputRouter>(router: router, viewController: router.viewController) }
      
      





モジュールの使用例
 private func presentCountryInput() { let module = InputModuleAssembly(title: "Add city", placeholder: "Country", doneButton: "Next") self.countryInputRouter = module.router module.router.configure( doneHandler: { [unowned self] country in self.interactor.setCountry(country) self.presentNameInput() } ) internalViewController?.viewControllers = [module.viewController] }
      
      





一般的に、 ルーターは次の目的必要です。









ルーターの例
 protocol IInputRouter: IBaseRouter { func configure(doneHandler: @escaping (String) -> ()) } final class InputRouter: IInputRouter { private let title: String private let placeholder: String private let doneButton: String let interactor: IInputInteractor private weak var internalViewController: IInputViewController? init(title: String, placeholder: String, doneButton: String) { self.title = title self.placeholder = placeholder self.doneButton = doneButton interactor = InputInteractor() } var viewController: UIViewController { if let _ = internalViewController { return internalViewController as! UIViewController } else { let vc = InputViewController(title: title, placeholder: placeholder, doneButton: doneButton) vc.router = self vc.interactor = interactor internalViewController = vc interactor.view = vc return vc } } func configure(doneHandler: @escaping (String) -> ()) { internalViewController?.doneHandler = doneHandler } }
      
      





モジュールで複数のアクションを実行できる場合、構成メソッドにはすべての可能なコールバックを含めることができます。 これにより、開発プロセス中に新しいコールバックを追加する場合に呼び出しを登録することを忘れないでください。







 //      callback, //     , //        . func configure(cancelHandler: @escaping () -> (), doneHandler: @escaping (String) -> ()) //       callback    , //      . func configure(cancelHandler: @escaping () -> ()) func configure(doneHandler: @escaping (String) -> ())
      
      





まったく同じ方法で、保存されたモジュールの形で、アプリケーションの最初の部分を表すことができます。







 class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? private weak var rootRouter: IRootRouter! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) self.window = window let module = RootModuleAssembly(window: window) rootRouter = module.router window.rootViewController = module.viewController window.makeKeyAndVisible() return true } }
      
      





依存関係は、 RootRouterで構成されているServiceLocatorから取得されます (ただし、明確にするためにRootInteractorに移動する価値があるかもしれません)。それに関連する2つの主なニュアンスがあります。









SILVERの一部として、 ルートモジュールは常にその責任の範囲内にあるため、常にそこにあると想定されます。









ServiceLocatorの例
 struct ServiceLocator { let geoStorage: IGeoStorageService func prepareInjections() { prepareInjection(geoStorage) } } func inject<T>() -> T! { let key = String(describing: T.self) return injections[key] as? T } fileprivate func prepareInjection<T: Any>(_ injection: T) { let key = String(describing: T.self) injections[key] = injection }
      
      





ServiceLocatorの作成例
 final class RootRouter: IRootRouter { // ... init(window: UIWindow) { let serviceLocator = ServiceLocator( geoStorage: GeoStorageService() ) serviceLocator.prepareInjections() } // ... }
      
      





ServiceLocatorの例
 final class ListInteractor: IListInteractor { // ... private lazy var geoStorageService: IGeoStorageService = inject() // pretty easy! // ... }
      
      





GitHubでデモプロジェクトを見る




All Articles