依存性注入は、システムを柔軟に構成し、このシステムのコンポーネントの相互依存関係を正しく構築できる、かなり一般的なパターンです。 入力のおかげで、Swiftでは、依存関係グラフを非常に簡単に説明できる便利なフレームワークを使用できます。 今日、これらのフレームワークの1つであるDITranquillity
について少しお話ししたいとDITranquillity
ます。
このチュートリアルでは、次のライブラリ機能について説明します。
- タイプ登録
- 初期化の展開
- 変数への埋め込み
- 循環コンポーネントの依存関係
UIStoryboard
ライブラリを使用する
コンポーネントの説明
アプリケーションは、 ViewController
、 Router
、 Presenter
、 Networking
主要なコンポーネントで構成されます-これらは、iOSアプリケーションで非常に一般的なコンポーネントです。
ViewController
とRouter
は相互に周期的に導入されます。
準備する
最初に、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) {
protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) {
protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() {
class ViewController: UIViewController { var presenter: Presenter! var router: Router! }
制限事項
他のクラスとは異なり、 ViewController
は私たちによって作成されるのではなく、 UIStoryboard.instantiateViewController
実装内のUIKitライブラリによって作成されるため、ストーリーボードを使用して、初期UIViewController
を使用してUIViewController
の継承者に依存関係を注入することはできません。 そのため、 UIView
とUITableViewCell
相続人がいUITableViewCell
。
プロトコルの背後に隠されたオブジェクトは、すべてのクラスに埋め込まれていることに注意してください。 これは、依存関係を実装する主なタスクの1つです。実装ではなく、インターフェイスに依存関係を作成します。 これは、将来、コンポーネントを再利用またはテストするための異なるプロトコル実装を提供するのに役立ちます。
依存性注入
システムのすべてのコンポーネントが作成された後、それらの間のオブジェクトの接続に進みます。 DITranquillityでは、出発点はDIContainer
、これはcontainer.register(...)
メソッドを使用して登録を追加します。 依存関係をパーツに分離するには、 DIFramework
とDIPart
ます。これらは実装する必要があります。 便宜上、1つのApplicationDependency
クラスのみを作成します。このクラスは、 DIFramework
を実装し、すべての依存関係の登録場所として機能します。 DIFramework
インターフェイスでは、メソッドload(container:)
1つだけ実装する必要があります。
class ApplicationDependency: DIFramework { static func load(container: DIContainer) {
依存関係のない最も単純なサインアップから始めましょうMyNetworking
container.register(MyNetworking.init)
この登録では、イニシャライザーを介した実装が使用されます。 コンポーネント自体には依存関係がないという事実にもかかわらず、コンポーネントの作成方法をライブラリに明確にするために初期化子を提供する必要があります。
同様に、 MyPresenter
とMyRouter
登録し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です。これにより、依存関係を簡潔に実装できます。 Router
とViewController
周期的に依存しているため、 cycle: true
で明示的に指定する必要がありcycle: true
。 ライブラリ自体は、明示的な指示なしにこれらの依存関係を解決できますが、この要件は、グラフを読んでいる人が依存関係チェーンにサイクルがあることをすぐに理解できるように導入されました。 ViewController.init
ではなく ViewController.self
ViewController.init
ていることにも注意してください。 これについては、上記の制限のセクションで説明しました。
また、特別な方法を使用してUIStoryboard
を登録する必要があります。
container.registerStoryboard(name: "Main")
これで、1つの画面の依存関係グラフ全体について説明しました。 ただし、このグラフにはまだアクセスできません。 その中のオブジェクトにアクセスできるDIContainer
を作成する必要があります。
static let container: DIContainer = { let container = DIContainer()
- コンテナを初期化する
- グラフの説明を追加します。
- すべてが正しく行われたことを確認します。 ミスをすると、依存関係の解決中にではなく、グラフの作成時にアプリケーションがクラッシュします
次に、コンテナをアプリケーションの開始点にする必要があります。 これを行うには、プロジェクト設定で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 }
打ち上げ
最初の起動時にドロップが発生し、次の理由で検証が失敗します。
- オブジェクトのみを登録したため、コンテナはタイプ
Router
、 Presenter
、 Networking
見つけません。 実装ではなくインターフェイスにアクセスを許可する場合は、インターフェイスを明示的に指定する必要があります - コンテナは循環依存関係を解決する方法を理解していません。グラフが解決されるたびに、どのオブジェクトを再作成すべきでないかを明示的に示す必要があるためです。
最初のエラーを修正するのは簡単です-コンテナ内のメソッドを使用できるプロトコルを指定できる特別なメソッドがあります。
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
ブレークポイントを設定して確認できます。
画面間の遷移
次に、 SecondViewController
とSecondPresenter
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
英語の記事