Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

More Decks by Yosuke Ishikawa

Other Decks in Technology

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