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

Static Dependency Injection by Code Generation

Yosuke Ishikawa
September 17, 2017

Static Dependency Injection by Code Generation

Yosuke Ishikawa

September 17, 2017
Tweet

More Decks by Yosuke Ishikawa

Other Decks in Technology

Transcript

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

    View Slide

  2. View Slide

  3. View Slide

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

    View Slide

  5. DI = Dependency Injection

    View Slide

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

    View Slide

  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()
    }
    }

    View Slide

  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()
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. final class APIClient {
    @Inject
    APIClient(urlSession: URLSession, cache: Cache) {
    ...
    }
    }
    Dagger
    のDI
    用のconstructor(Java)

    View Slide

  22. View Slide

  23. provider method =
    インスタンスを提供するメソッド

    View Slide

  24. @Module
    final class APIModule {
    @Provides
    static APIClient provideAPIClient(urlSession: URLSession, cache: Cache)
    ...
    }
    }
    Dagger
    のDI
    用のprovider method(Java)

    View Slide

  25. 各ノードの定義ができるようになった

    View Slide

  26. グラフの生成

    View Slide

  27. View Slide

  28. @Generated
    public 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);
    }
    @Override
    public APIClient apiClient() {
    return apiClientProvider.get()
    }
    }
    生成されたコードで依存関係の解決を表現(Java)

    View Slide

  29. Swi
    での実践

    View Slide

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

    View Slide

  31. DI
    に使用できるinitializer

    View Slide

  32. final class APIClient {
    @Inject
    APIClient(urlSession: URLSession, cache: Cache) {
    ...
    }
    }
    Dagger
    のDI
    用のconstructor(Java)

    View Slide

  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

    View Slide

  34. DI
    に使用できるprovider method

    View Slide

  35. @Module
    final class APIModule {
    @Provides
    static APIClient provideAPIClient(urlSession: URLSession, cache: Cache)
    ...
    }
    }
    Dagger
    のDI
    用のprovider method(Java)

    View Slide

  36. protocol Resolver {}

    View Slide

  37. protocol AppResolver: Resolver {
    func provideURLSession() -> URLSession
    func provideCache() -> URLSession
    func provideAPIClient(urlSession: URLSession, cache: Cache) -> APIClient
    }
    Swi
    のDI
    用のprovider method

    View Slide

  38. 依存関係を解決するコード生成

    View Slide

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

    View Slide

  40. SourceKitten

    View Slide

  41. {
    "key.accessibility" : "source.lang.swift.accessibility.internal",
    "key.kind" : "source.lang.swift.decl.struct",
    "key.name" : "A",
    "key.inheritedtypes" : [
    {
    "key.name" : "Injectable"
    }
    ]
    }

    View Slide

  42. Resolver
    のprotocol extension

    インスタンスを取得するメソッドを生やす

    View Slide

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

    View Slide

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

    View Slide

  45. final class AppABResolver {
    func provideB() {
    return B()
    }
    }

    View Slide

  46. View Slide

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

    View Slide

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

    View Slide

  49. すべてのdependency
    が自動的に解決できるものとは限らない

    resolve
    メソッドのパラメーターとして渡す必要がある場合もある

    View Slide

  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
    }

    View Slide

  51. extension AppResolver {
    func resolveAPIClient() -> APIClient {
    return provideAPIClient()
    }
    func resolveUserProfileViewController(userID: Int64) -> UserProfileViewContr
    let apiClient = resolveAPIClient()
    return UserProfileViewController(dependency: .init(userID: userID, apiCl
    }
    }

    View Slide

  52. View Slide

  53. デモ

    View Slide

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

    View Slide