swift-dependenciesによるDI

3K Views

February 29, 24

スライド概要

関西モバイルアプリ研究会A #2 - connpass
https://kanmoba.connpass.com/event/308691/

DIライブラリswift-dependenciesの話をしました #関モバ - usami-kの日記
https://usami-k.hatenablog.com/entry/2024/02/29/233722

profile-image

https://usami-k.github.io/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

swift-dependenciesによるDI structによるインターフェイス定義 宇佐見公輔 / 株式会社ゆめみ 2024-02-29 swift-dependenciesによるDI 1

2.

swift-dependenciesとは Point-FreeによるDI(Dependency Injection)ライブラリ。 https://github.com/pointfreeco/swift-dependencies バージョン 0.1.0 : 2023年1月 1.0.0 : 2023年7月 1.2.1 : 2024年2月 The Composable Architecture(TCA)の一部でもある。 swift-dependenciesによるDI 2

3.

参考記事 次の記事が分かりやすいのでオススメ。 swift-dependencies の使い方・メリット・注意点 なお、今回の話では上述の記事に書かれていない点も取り上げる。 swift-dependenciesによるDI 3

4.

依存オブジェクトの注入 @Dependency プロパティラッパーを使う。 final class MainViewModel { @Dependency(\.myAPIClient) private var myAPIClient // ... do { let result = try await myAPIClient.fetch() } } swift-dependenciesによるDI 4

5.

@Environment との類似 swift-dependenciesの記法は、SwiftUIの @Environment と似ている。 struct MainScreen: View { @Environment(\.openURL) private var openURL // ... Button("Open") { openURL(url) } } swift-dependenciesによるDI 5

6.

依存オブジェクトの実装 プロトコルで依存オブジェクトのインターフェイスを定義しておく。 protocol MyAPIClientProtocol { func fetch() async throws -> Int } struct MyAPIClient: MyAPIClientProtocol { func fetch() async throws -> Int { // ... return number } } swift-dependenciesによるDI 6

7.

依存オブジェクトの登録 DependencyKey プロトコルを使う。 private enum MyAPIClientKey: DependencyKey { static let liveValue: any MyAPIClientProtocol = MyAPIClient() } extension DependencyValues { var myAPIClient: any MyAPIClientProtocol { get { self[MyAPIClientKey.self] } set { self[MyAPIClientKey.self] = newValue } } } swift-dependenciesによるDI 7

8.

カスタムEnvironmentとの類似 やはり、SwiftUIの @Environment の方法と似ている。 struct MyEnvironmentKey: EnvironmentKey { static let defaultValue: String = "Default value" } extension EnvironmentValues { var myEnvironment: String { get { self[MyEnvironmentKey.self] } set { self[MyEnvironmentKey.self] = newValue } } } swift-dependenciesによるDI 8

9.

注入するオブジェクトを変更する テストで使うオブジェクトを liveValue 以外のものに変更できる。 struct MyAPIClientMock: MyAPIClientProtocol { // ... } private enum MyAPIClientKey: TestDependencyKey { static let testValue: any MyAPIClientProtocol = unimplemented() static let previewValue: any MyAPIClientProtocol = MyAPIClientMock() } swift-dependenciesによるDI 9

10.

注入するオブジェクトを個別変更する withDependencies で個別変更できる。 let viewModel = withDependencies { $0.myAPIClient = MyAPIClientMock(fetchResult: .success(42)) } operation: { MainViewModel() } swift-dependenciesによるDI 10

11.

インターフェイスの定義のしかた ここまでの方法:プロトコルでインターフェイスを定義。 protocol MyAPIClientProtocol { func fetch() async throws -> Int } 推奨:structとクロージャでインターフェイスを定義。 struct MyAPIClient { var fetch: () async throws -> Int } swift-dependenciesによるDI 11

12.

依存オブジェクトの実装と登録 @DependencyClient struct MyAPIClient { var fetch: () async throws -> Int } extension MyAPIClient: DependencyKey { static var liveValue: MyAPIClient = { return .init(fetch: { // ... return number }) }() } swift-dependenciesによるDI 12

13.

@DependencyClient testValue マクロ 向けの実装を自動生成してくれる。 @DependencyClient struct MyAPIClient { var fetch: () async throws -> Int } extension MyAPIClient: TestDependencyKey { static var testValue = Self() } swift-dependenciesによるDI 13

14.

依存オブジェクトの注入 final class MainViewModel { @Dependency(MyAPIClient.self) private var myAPIClient // ... do { let result = try await myAPIClient.fetch() } } なお、 DependencyValues swift-dependenciesによるDI のextensionは不要。 14

15.

注入するオブジェクトを変更する let viewModel = withDependencies { $0[MyAPIClient.self].fetch = { return 42 } } operation: { MainViewModel() } ここでも、 DependencyValues swift-dependenciesによるDI のextensionは不要。 15

16.

メソッドだけの注入 @Dependency(\.myAPIClient.fetch) private var fetch do { let result = try await fetch() } extension DependencyValues { var myAPIClient: MyAPIClient { // ← get { self[MyAPIClient.self] } set { self[MyAPIClient.self] = newValue } } } この注入方法では、これが必要になる swift-dependenciesによるDI 16

17.

structインターフェイスの利点 プロトコルの場合は 、 、 、 とそれぞれ定義が必要。 structの場合は だけですみ、シンプルになる。 マクロも活用できる。 ただ、メソッドをクロージャで に渡す記法は慣れがいるかも。 MyAPIClientProtocol MyAPIClientMock MyAPIClient MyAPIClientKey MyAPIClient @DependencyClient init .init(method1: {}, method2: {}, method3: {}) swift-dependenciesによるDI 17

18.

応用 注入できるスコープは、メソッドや計算プロパティもあり。 func foo() { @Dependency(MyLogger.self) private var logger logger.notice("log messages") } ただし、公式ドキュメントによると、ライブラリを十分に理解してか ら使うべきとのこと。 ログやトラッキングなどの用途では問題なさそう。 swift-dependenciesによるDI 18

19.

swift-dependenciesまとめ SwiftUIの に近い感覚で使える。 structとクロージャによるインターフェイスの実装方法を使うと、 少ない実装コードでDIができる。 注意すべき点もあるので、公式ドキュメントはちゃんと読むことを 推奨。 @Environment swift-dependenciesによるDI 19