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 full-size slide

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

    View full-size slide

  3. DI = Dependency Injection

    View full-size slide

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

    View full-size slide

  5. 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 full-size slide

  6. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. グラフの生成

    View full-size slide

  23. @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 full-size slide

  24. Swi
    での実践

    View full-size slide

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

    View full-size slide

  26. DI
    に使用できるinitializer

    View full-size slide

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

    View full-size slide

  28. 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 full-size slide

  29. DI
    に使用できるprovider method

    View full-size slide

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

    View full-size slide

  31. protocol Resolver {}

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. SourceKitten

    View full-size slide

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

    View full-size slide

  37. Resolver
    のprotocol extension

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

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

    View full-size slide

  44. 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 full-size slide

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

    View full-size slide

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

    View full-size slide