コード生成による静的なDependency Injection
View Slide
Dependency Injection(DI)の概要DIをサポートする仕組みがなぜ必要かコード生成による静的なDI
DI = Dependency Injection
DI =必要なものを外から渡すDependency:必要なものInjection:外から渡す
final class ImageDownloader {private let urlSession = URLSession.sharedfunc downloadImage(with url: URL, completion: (UIImage) -> Void) {let task = urlSession.dataTask(with: url) {...}task.resume()}}
final class ImageDownloader {private let urlSession: URLSessioninit(urlSession: URLSession) {self.urlSession = urlSession}func downloadImage(with url: URL, completion: (UIImage) -> Void) {let task = urlSession.dataTask(with: url) {...}task.resume()}}
dependencyの切り替えが可能dependencyの詳細を必要以上に決めずに済む
IoC: Inversion of ControlDIP: Dependency Inversion Principle
デメリットはあるの?
dependencyのインスタンスを取得して渡す責務が外側に生じる
DIなし:密結合だがインスタンスの生成は簡単DIあり:疎結合だがインスタンスの生成が面倒
DIなし:密結合だがインスタンスの生成は簡単DIあり:疎結合だがインスタンスの生成が面倒???:疎結合だしインスタンスの生成が簡単
DIをサポートする仕組み
右側のインスタンスの取得を自動化する
自動的にインスタンスを生成できる型を方法とセットで登録しておく
インスタンスを自動的に取得する方法DI用のinitializerを登録するDI用のprovider methodを用意する
final class APIClient {@InjectAPIClient(urlSession: URLSession, cache: Cache) {...}}DaggerのDI用のconstructor(Java)
provider method =インスタンスを提供するメソッド
@Modulefinal class APIModule {@Providesstatic APIClient provideAPIClient(urlSession: URLSession, cache: Cache)...}}DaggerのDI用のprovider method(Java)
各ノードの定義ができるようになった
グラフの生成
@Generatedpublic final class DaggerAPIComponent implements APIComponent {private Provider urlSessionProvider;private Provider cacheProvider;private Provider apiClientProvider;private void initialize() {urlSessionProvider = ...cacheProvider = ...apiClientProvider = APIModule_APIClientFactory.create(urlSessionProvider, cacheProvider);}@Overridepublic APIClient apiClient() {return apiClientProvider.get()}}生成されたコードで依存関係の解決を表現(Java)
Swiでの実践
DIに使用できるinitializerの定義DIに使用できるprovider methodの定義コード生成による依存関係の解決
DIに使用できるinitializer
protocol Injectable {associatedtype Dependencyinit(dependency: Dependency)}final class APIClient: Injectable {struct Dependency {let urlSession: URLSessionlet cache: Cache}init(dependency: Dependency) {...}}SwiのDI用のinitializer
DIに使用できるprovider method
protocol Resolver {}
protocol AppResolver: Resolver {func provideURLSession() -> URLSessionfunc provideCache() -> URLSessionfunc provideAPIClient(urlSession: URLSession, cache: Cache) -> APIClient}SwiのDI用のprovider method
依存関係を解決するコード生成
記述されているコードを解釈して、それを補完するコードを生成
SourceKitten
{"key.accessibility" : "source.lang.swift.accessibility.internal","key.kind" : "source.lang.swift.decl.struct","key.name" : "A","key.inheritedtypes" : [{"key.name" : "Injectable"}]}
Resolverのprotocol extensionにインスタンスを取得するメソッドを生やす
struct A: Injectable {struct Dependency {let b: B}init(dependency: Dependency) {}}struct B {}protocol ABResolver: Resolver {func provideB() -> B}
extension ABResolver {func resolveA() -> A {let b = resolveB()return A(dependency: .init(b: b))}func resolveB() -> B {return provideB()}}provideB()の実装はABResolverの準拠側に委ねられる
final class AppABResolver {func provideB() {return B()}}
プロトコルにした意味は?どう提供するかは決めていない状態でグラフを組める必要なものが揃っていることがコンパイル時に保証される
自動的に取得できないdependencyは?
すべてのdependencyが自動的に解決できるものとは限らない↓resolveメソッドのパラメーターとして渡す必要がある場合もある
final class UserProfileViewController: UIViewController, Injectable {struct Dependency {let userID: Int64let apiClient: APIClient}init(dependency: Dependency) {}}final class APIClient {...}protocol AppResolver: Resolver {func provideAPIClient() -> APIClient}
extension AppResolver {func resolveAPIClient() -> APIClient {return provideAPIClient()}func resolveUserProfileViewController(userID: Int64) -> UserProfileViewContrlet apiClient = resolveAPIClient()return UserProfileViewController(dependency: .init(userID: userID, apiCl}}
デモ
https://github.com/ishkawa/DIKit