Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

DI = Dependency Injection

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

IoC: Inversion of Control DIP: Dependency Inversion Principle

Slide 11

Slide 11 text

デメリットはあるの?

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

DI をサポートする仕組み

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

グラフの生成

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

@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)

Slide 29

Slide 29 text

Swi での実践

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

DI に使用できるinitializer

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

DI に使用できるprovider method

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

protocol Resolver {}

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

SourceKitten

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Resolver のprotocol extension に インスタンスを取得するメソッドを生やす

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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 }

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

デモ

Slide 54

Slide 54 text

https://github.com/ishkawa/DIKit