Static Dependency Injection by Code Generation

8889da6a67db3667b0694d993c9a962c?s=47 Yosuke Ishikawa
September 17, 2017

Static Dependency Injection by Code Generation

8889da6a67db3667b0694d993c9a962c?s=128

Yosuke Ishikawa

September 17, 2017
Tweet

Transcript

  1. コード生成による 静的な Dependency Injection

  2. None
  3. None
  4. Dependency Injection(DI) の概要 DI をサポートする仕組みがなぜ必要か コード生成による静的なDI

  5. DI = Dependency Injection

  6. DI = 必要なものを外から渡す Dependency: 必要なもの Injection: 外から渡す

  7. final class ImageDownloader { private let urlSession = URLSession.shared func

    downloadImage(with url: URL, completion: (UIImage) -> Void) { let task = urlSession.dataTask(with: url) {...} task.resume() } }
  8. final class ImageDownloader { private let urlSession: URLSession init(urlSession: URLSession)

    { self.urlSession = urlSession } func downloadImage(with url: URL, completion: (UIImage) -> Void) { let task = urlSession.dataTask(with: url) {...} task.resume() } }
  9. dependency の切り替えが可能 dependency の詳細を必要以上に決めずに済む

  10. IoC: Inversion of Control DIP: Dependency Inversion Principle

  11. デメリットはあるの?

  12. dependency の切り替えが可能 dependency の詳細を必要以上に決めずに済む

  13. dependency のインスタンスを取得して渡す責務が外側に生じる

  14. None
  15. DI なし: 密結合だがインスタンスの生成は簡単 DI あり: 疎結合だがインスタンスの生成が面倒

  16. DI なし: 密結合だがインスタンスの生成は簡単 DI あり: 疎結合だがインスタンスの生成が面倒 ???: 疎結合だしインスタンスの生成が簡単

  17. DI をサポートする仕組み

  18. 右側のインスタンスの取得を自動化する

  19. 自動的にインスタンスを生成できる型を 方法とセットで登録しておく

  20. インスタンスを自動的に取得する方法 DI 用のinitializer を登録する DI 用のprovider method を用意する

  21. final class APIClient { @Inject APIClient(urlSession: URLSession, cache: Cache) {

    ... } } Dagger のDI 用のconstructor(Java)
  22. None
  23. provider method = インスタンスを提供するメソッド

  24. @Module final class APIModule { @Provides static APIClient provideAPIClient(urlSession: URLSession,

    cache: Cache) ... } } Dagger のDI 用のprovider method(Java)
  25. 各ノードの定義ができるようになった

  26. グラフの生成

  27. None
  28. @Generated public final class DaggerAPIComponent implements APIComponent { private Provider<URLSession>

    urlSessionProvider; private Provider<Cache> cacheProvider; private Provider<APIClient> apiClientProvider; private void initialize() { urlSessionProvider = ... cacheProvider = ... apiClientProvider = APIModule_APIClientFactory .create(urlSessionProvider, cacheProvider); } @Override public APIClient apiClient() { return apiClientProvider.get() } } 生成されたコードで依存関係の解決を表現(Java)
  29. Swi での実践

  30. DI に使用できるinitializer の定義 DI に使用できるprovider method の定義 コード生成による依存関係の解決

  31. DI に使用できるinitializer

  32. final class APIClient { @Inject APIClient(urlSession: URLSession, cache: Cache) {

    ... } } Dagger のDI 用のconstructor(Java)
  33. protocol Injectable { associatedtype Dependency init(dependency: Dependency) } final class

    APIClient: Injectable { struct Dependency { let urlSession: URLSession let cache: Cache } init(dependency: Dependency) {...} } Swi のDI 用のinitializer
  34. DI に使用できるprovider method

  35. @Module final class APIModule { @Provides static APIClient provideAPIClient(urlSession: URLSession,

    cache: Cache) ... } } Dagger のDI 用のprovider method(Java)
  36. protocol Resolver {}

  37. protocol AppResolver: Resolver { func provideURLSession() -> URLSession func provideCache()

    -> URLSession func provideAPIClient(urlSession: URLSession, cache: Cache) -> APIClient } Swi のDI 用のprovider method
  38. 依存関係を解決するコード生成

  39. 記述されているコードを解釈して、それを補完するコードを生成

  40. SourceKitten

  41. { "key.accessibility" : "source.lang.swift.accessibility.internal", "key.kind" : "source.lang.swift.decl.struct", "key.name" : "A",

    "key.inheritedtypes" : [ { "key.name" : "Injectable" } ] }
  42. Resolver のprotocol extension に インスタンスを取得するメソッドを生やす

  43. struct A: Injectable { struct Dependency { let b: B

    } init(dependency: Dependency) {} } struct B {} protocol ABResolver: Resolver { func provideB() -> B }
  44. extension ABResolver { func resolveA() -> A { let b

    = resolveB() return A(dependency: .init(b: b)) } func resolveB() -> B { return provideB() } } provideB() の実装はABResolver の準拠側に委ねられる
  45. final class AppABResolver { func provideB() { return B() }

    }
  46. None
  47. プロトコルにした意味は? どう提供するかは決めていない状態でグラフを組める 必要なものが揃っていることがコンパイル時に保証される

  48. 自動的に取得できないdependency は?

  49. すべてのdependency が自動的に解決できるものとは限らない ↓ resolve メソッドのパラメーターとして渡す必要がある場合もある

  50. final class UserProfileViewController: UIViewController, Injectable { struct Dependency { let

    userID: Int64 let apiClient: APIClient } init(dependency: Dependency) {} } final class APIClient {...} protocol AppResolver: Resolver { func provideAPIClient() -> APIClient }
  51. extension AppResolver { func resolveAPIClient() -> APIClient { return provideAPIClient()

    } func resolveUserProfileViewController(userID: Int64) -> UserProfileViewContr let apiClient = resolveAPIClient() return UserProfileViewController(dependency: .init(userID: userID, apiCl } }
  52. None
  53. デモ

  54. https://github.com/ishkawa/DIKit