Static Dependency Injection by Code Generation
by
Yosuke Ishikawa
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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