@@ -6,17 +6,18 @@ available from any part of your code base.
6
6
## Overview
7
7
8
8
Although the library comes with many controllable dependencies out of the box, there are still times
9
- when you want to register your own dependencies with the library so that you can use the
10
- `` Dependency `` property wrapper. Doing this is a two-step process and is quite similar to
11
- registering an [ environment value ] [ environment-values-docs ] in SwiftUI.
9
+ when you want to register your own dependencies with the library so that you can use them with the
10
+ `` Dependency `` property wrapper. There are a couple ways to achieve this, and the process is quite
11
+ similar to registering a value with [ the environment ] [ environment-values-docs ] in SwiftUI.
12
12
13
- First you create a type that conforms to the `` DependencyKey `` protocol. The minimum implementation
14
- you must provide is a `` DependencyKey/liveValue `` , which is the value used when running the app in a
13
+ First you create a `` DependencyKey `` protocol conformance . The minimum implementation you must
14
+ provide is a `` DependencyKey/liveValue `` , which is the value used when running the app in a
15
15
simulator or on device, and so it's appropriate for it to actually make network requests to an
16
- external server:
16
+ external server. It is usually convenient to conform the type of dependency directly to this
17
+ protocol:
17
18
18
19
``` swift
19
- private enum APIClientKey : DependencyKey {
20
+ extension APIClient : DependencyKey {
20
21
static let liveValue = APIClient (/*
21
22
Construct the "live" API client that actually makes network
22
23
requests and communicates with the outside world.
@@ -30,24 +31,11 @@ private enum APIClientKey: DependencyKey {
30
31
> need to worry about those values when you are just getting started, and instead can add them
31
32
> later. See < Doc:LivePreviewTest > for more information.
32
33
33
- Finally, an extension must be made to ` DependencyValues ` to expose a computed property for the
34
- dependency:
35
-
36
- ``` swift
37
- extension DependencyValues {
38
- var apiClient: APIClient {
39
- get { self [APIClientKey.self ] }
40
- set { self [APIClientKey.self ] = newValue }
41
- }
42
- }
43
- ```
44
-
45
- With those few steps completed you can instantly access your API client dependency from any part of
46
- you code base:
34
+ With that done you can instantly access your API client dependency from any part of your code base:
47
35
48
36
``` swift
49
37
final class TodosModel : ObservableObject {
50
- @Dependency (\. apiClient ) var apiClient
38
+ @Dependency (APIClient. self ) var apiClient
51
39
// ...
52
40
}
53
41
```
@@ -59,7 +47,7 @@ you can override the dependency to return mock data:
59
47
@MainActor
60
48
func testFetchUser () async {
61
49
let model = withDependencies {
62
- $0 . apiClient .fetchTodos = { _ in Todo (id : 1 , title : " Get milk" ) }
50
+ $0 [APIClient. self ] .fetchTodos = { _ in Todo (id : 1 , title : " Get milk" ) }
63
51
} operation : {
64
52
TodosModel ()
65
53
}
@@ -72,26 +60,82 @@ func testFetchUser() async {
72
60
}
73
61
```
74
62
75
- Often times it is not necessary to create a whole new type to conform to ` DependencyKey ` . If the
76
- dependency you are registering is a type that you own, then you can conform it directly to the
77
- protocol:
63
+ ## Advanced techniques
78
64
79
- ``` swift
80
- extension APIClient : DependencyKey {
81
- static let liveValue = APIClient (/*
82
- Construct the "live" API client that actually makes network
83
- requests and communicates with the outside world.
84
- */ )
85
- }
65
+ ### Dependency key paths
86
66
67
+ You can take one additional step to register your dependency value at a particular key path, and
68
+ that is by extending ` DependencyValues ` with a property:
69
+
70
+ ``` swift
87
71
extension DependencyValues {
88
72
var apiClient: APIClient {
89
- get { self [APIClient .self ] }
90
- set { self [APIClient .self ] = newValue }
73
+ get { self [APIClientKey .self ] }
74
+ set { self [APIClientKey .self ] = newValue }
91
75
}
92
76
}
93
77
```
94
78
95
- That can save a little bit of boilerplate.
79
+ This allows you to access and override the dependency in way similar to SwiftUI environment values,
80
+ as a property that is discoverable from autocomplete:
81
+
82
+ ``` diff
83
+ - @Dependency(APIClient.self) var apiClient
84
+ + @Dependency(\.apiClient) var apiClient
85
+
86
+ let model = withDependencies {
87
+ - $0[APIClient.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") }
88
+ + $0.apiClient.fetchTodos = { _ in Todo(id: 1, title: "Get milk") }
89
+ } operation: {
90
+ TodosModel()
91
+ }
92
+ ```
93
+
94
+ Another benefit of this style is the ability to scope a ` @Dependency ` to a specific sub-property:
95
+
96
+ ``` swift
97
+ // This feature only needs to access the API client's logged-in user
98
+ @Dependency (\.apiClient .currentUser ) var currentUser
99
+ ```
100
+
101
+ ### Indirect dependency key conformances
102
+
103
+ It is not always appropriate to conform your dependency directly to the ` DependencyKey ` protocol,
104
+ for example if it is a type you do not own. In such cases you can define a separate type that
105
+ conforms to ` DependencyKey ` :
106
+
107
+ ``` swift
108
+ enum UserDefaultsKey : DependencyKey {
109
+ static let liveValue = UserDefaults.standard
110
+ }
111
+ ```
112
+
113
+ You can then access and override your dependency through this key type, instead of the value's type:
114
+
115
+ ``` swift
116
+ @Dependency (UserDefaultsKey.self ) var userDefaults
117
+
118
+ let model = withDependencies {
119
+ let defaults = UserDefaults (suiteName : " test-defaults" )
120
+ defaults.removePersistentDomain (forName : " test-defaults" )
121
+ $0 [UserDefaultsKey.self ] = defaults
122
+ } operation : {
123
+ TodosModel ()
124
+ }
125
+ ```
126
+
127
+ If you extend dependency values with a dedicated key path, you can even make this key private:
128
+
129
+ ``` diff
130
+ - enum UserDefaultsKey: DependencyKey { /* ... */ }
131
+ + private enum UserDefaultsKey: DependencyKey { /* ... */ }
132
+ +
133
+ + extension DependencyValues {
134
+ + var userDefaults: APIClient {
135
+ + get { self[UserDefaultsKey.self] }
136
+ + set { self[UserDefaultsKey.self] = newValue }
137
+ + }
138
+ + }
139
+ ```
96
140
97
141
[ environment-values-docs ] : https://developer.apple.com/documentation/swiftui/environmentvalues
0 commit comments