DITranquillityによる依存性注入

依存性注入は、システムを柔軟に構成し、このシステムのコンポーネントの相互依存関係を正しく構築できる、かなり一般的なパターンです。 入力のおかげで、Swiftでは、依存関係グラフを非常に簡単に説明できる便利なフレームワークを使用できます。 今日、これらのフレームワークの1つであるDITranquillityについて少しお話ししたいとDITranquillityます。


このチュートリアルでは、次のライブラリ機能について説明します。



コンポーネントの説明


アプリケーションは、 ViewControllerRouterPresenterNetworking主要なコンポーネントで構成されます-これらは、iOSアプリケーションで非常に一般的なコンポーネントです。


コンポーネント構造

ViewControllerRouterは相互に周期的に導入されます。


準備する


最初に、Xcodeでシングルビューアプリケーションを作成し、CocoaPodsを使用してDITranquillityを追加します 。 必要なファイルの階層を作成し、2番目のコントローラーをMain.storyboardに追加し、StoryboardSegueを使用して接続します。 その結果、次のファイル構造が取得されます。


ファイル構造

次のように、クラスに依存関係を作成します。


コンポーネント宣言
 protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) { // Implementation } } 

 protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) { // Implementation } } 

 protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() { // Implementation } } 

 class ViewController: UIViewController { var presenter: Presenter! var router: Router! } 

制限事項


他のクラスとは異なり、 ViewControllerは私たちによって作成されるのではなく、 UIStoryboard.instantiateViewController実装内のUIKitライブラリによって作成されるため、ストーリーボードを使用して、初期UIViewControllerを使用してUIViewControllerの継承者に依存関係を注入することはできません。 そのため、 UIViewUITableViewCell相続人がいUITableViewCell


プロトコルの背後に隠されたオブジェクトは、すべてのクラスに埋め込まれていることに注意してください。 これは、依存関係を実装する主なタスクの1つです。実装ではなく、インターフェイスに依存関係を作成します。 これは、将来、コンポーネントを再利用またはテストするための異なるプロトコル実装を提供するのに役立ちます。


依存性注入


システムのすべてのコンポーネントが作成された後、それらの間のオブジェクトの接続に進みます。 DITranquillityでは、出発点はDIContainer 、これはcontainer.register(...)メソッドを使用して登録を追加します。 依存関係をパーツに分離するには、 DIFrameworkDIPartます。これらは実装する必要があります。 便宜上、1つのApplicationDependencyクラスのみを作成します。このクラスは、 DIFrameworkを実装し、すべての依存関係の登録場所として機能します。 DIFrameworkインターフェイスでは、メソッドload(container:) 1つだけ実装する必要があります。


 class ApplicationDependency: DIFramework { static func load(container: DIContainer) { // registrations will be placed here } } 

依存関係のない最も単純なサインアップから始めましょうMyNetworking


 container.register(MyNetworking.init) 

この登録では、イニシャライザーを介した実装が使用されます。 コンポーネント自体には依存関係がないという事実にもかかわらず、コンポーネントの作成方法をライブラリに明確にするために初期化子を提供する必要があります。


同様に、 MyPresenterMyRouter登録しMyRouter


 container.register1(MyPresenter.init) container.register1(MyRouter.init) 

注: registerは使用されず、 registerが使用されることに注意してください。 残念ながら、これは、オブジェクトの初期化子に依存関係が1つだけあるかどうかを示すために必要です。 つまり、0または2つ以上の依存関係がある場合は、 registerを使用するだけです。 この制限は、Swiftバージョン4.0以降のバグです。


ViewControllerを登録します。 初期化子ではなく変数に直接オブジェクトを注入するため、登録の説明はもう少し多くなります。


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) 

\.presenterの形式の構文はSwiftKeyPathです。これにより、依存関係を簡潔に実装できます。 RouterViewController周期的に依存しているため、 cycle: trueで明示的に指定する必要がありcycle: true 。 ライブラリ自体は、明示的な指示なしにこれらの依存関係を解決できますが、この要件は、グラフを読んでいる人が依存関係チェーンにサイクルがあることをすぐに理解できるように導入されました。 ViewController.initではなく ViewController.self ViewController.initていることにも注意してください。 これについては、上記の制限のセクションで説明しました。


また、特別な方法を使用してUIStoryboardを登録する必要があります。


 container.registerStoryboard(name: "Main") 

これで、1つの画面の依存関係グラフ全体について説明しました。 ただし、このグラフにはまだアクセスできません。 その中のオブジェクトにアクセスできるDIContainerを作成する必要があります。


 static let container: DIContainer = { let container = DIContainer() // 1 container.append(framework: ApplicationDependency.self) // 2 assert(container.validate(checkGraphCycles: true)) // 3 return container }() 

  1. コンテナを初期化する
  2. グラフの説明を追加します。
  3. すべてが正しく行われたことを確認します。 ミスをすると、依存関係の解決中にではなく、グラフの作成時にアプリケーションがクラッシュします

次に、コンテナをアプリケーションの開始点にする必要があります。 これを行うには、プロジェクト設定でMain.storyboardを起動ポイントとして指定する代わりに、 didFinishLaunchingWithOptionsメソッドAppDelegate実装します。


 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true } 

打ち上げ


最初の起動時にドロップが発生し、次の理由で検証が失敗します。



最初のエラーを修正するのは簡単です-コンテナ内のメソッドを使用できるプロトコルを指定できる特別なメソッドがあります。


 container.register(MyNetworking.init) .as(check: Networking.self) {$0} 

登録を次のように説明しますMyNetworkingオブジェクトには、 Networkingプロトコルを介してアクセスできます。 これは、プロトコルの下に隠されているすべてのオブジェクトに対して実行する必要があります。 {$0}コンパイラによる正しい型チェックのため{$0}追加します。


2番目の間違いはもう少し複雑です。 いわゆるscopeを使用する必要があります。これは、オブジェクトが作成される頻度とオブジェクトがどれだけ存続するかを記述します。 循環依存関係に参加する登録ごとに、 objectGraphと等しいscope指定する必要があります。 これにより、コンテナに対して、毎回作成するのではなく、作成時に同じ作成済みオブジェクトを再利用する必要があることが明確になります。 したがって、判明します:


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph) 

再起動後、コンテナは検証に成功し、作成された依存関係でViewControllerが開きます。 viewDidLoadブレークポイントを設定して確認できます。


画面間の遷移


次に、 SecondViewControllerSecondPresenter 2つの小さなクラスを作成し、 SecondViewControllerをストーリーボードに追加し、 "RouteToSecond"という識別子を使用してそれらの間にSegueを作成します。


新しいクラスごとにApplicationDependencyさらに2つの登録を追加します。


 container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init) 

SecondPresenterプロトコルの背後に隠さず、実装を直接使用するため、 .asを指定する必要.asません。 次に、最初のコントローラーのviewDidAppearメソッドで、 performSegue(withIdentifier: "RouteToSecond", sender: self)を呼び出して、2番目のコントローラーを開き、 secondPresenter依存関係をsecondPresenterます。 ご覧のとおり、コンテナーはUIStoryboardから2番目のコントローラーを作成し、依存関係を正常に配置しました。


おわりに


このライブラリを使用すると、循環依存関係、ストーリーボードを簡単に操作でき、Swiftの自動型推論を完全に使用して、依存関係グラフを記述するための非常に短く柔軟な構文を提供します。


参照資料


githubライブラリの完全なサンプルコード


githubの DITranquillity


英語の記事



Source: https://habr.com/ru/post/J450788/


All Articles