Skip to content

Commit 09e49dd

Browse files
authored
Update documentation to use dependency key shorthand (#176)
* Update documentation with key shorthand * wip * wip * wip
1 parent 7417942 commit 09e49dd

File tree

1 file changed

+80
-36
lines changed

1 file changed

+80
-36
lines changed

Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ available from any part of your code base.
66
## Overview
77

88
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.
1212

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
1515
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:
1718

1819
```swift
19-
private enum APIClientKey: DependencyKey {
20+
extension APIClient: DependencyKey {
2021
static let liveValue = APIClient(/*
2122
Construct the "live" API client that actually makes network
2223
requests and communicates with the outside world.
@@ -30,24 +31,11 @@ private enum APIClientKey: DependencyKey {
3031
> need to worry about those values when you are just getting started, and instead can add them
3132
> later. See <Doc:LivePreviewTest> for more information.
3233
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:
4735

4836
```swift
4937
final class TodosModel: ObservableObject {
50-
@Dependency(\.apiClient) var apiClient
38+
@Dependency(APIClient.self) var apiClient
5139
// ...
5240
}
5341
```
@@ -59,7 +47,7 @@ you can override the dependency to return mock data:
5947
@MainActor
6048
func testFetchUser() async {
6149
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") }
6351
} operation: {
6452
TodosModel()
6553
}
@@ -72,26 +60,82 @@ func testFetchUser() async {
7260
}
7361
```
7462

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
7864

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
8666

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
8771
extension DependencyValues {
8872
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 }
9175
}
9276
}
9377
```
9478

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+
```
96140

97141
[environment-values-docs]: https://developer.apple.com/documentation/swiftui/environmentvalues

0 commit comments

Comments
 (0)