From f66416d4bd562e4d9f680ac00ef32325f11a94ec Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Jan 2024 11:14:37 -0800 Subject: [PATCH 1/4] Update documentation with key shorthand --- .../Articles/RegisteringDependencies.md | 102 +++++++++++------- 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md index c4b33c90..150ae3fa 100644 --- a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md +++ b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md @@ -6,17 +6,18 @@ available from any part of your code base. ## Overview Although the library comes with many controllable dependencies out of the box, there are still times -when you want to register your own dependencies with the library so that you can use the -``Dependency`` property wrapper. Doing this is a two-step process and is quite similar to -registering an [environment value][environment-values-docs] in SwiftUI. +when you want to register your own dependencies with the library so that you can use them with the +``Dependency`` property wrapper. There are a couple ways to achieve this, and the process is quite +similar to registering a value with [the environment][environment-values-docs] in SwiftUI. -First you create a type that conforms to the ``DependencyKey`` protocol. The minimum implementation -you must provide is a ``DependencyKey/liveValue``, which is the value used when running the app in a +First you create a ``DependencyKey`` protocol conformance. The minimum implementation you must +provide is a ``DependencyKey/liveValue``, which is the value used when running the app in a simulator or on device, and so it's appropriate for it to actually make network requests to an -external server: +external server. It is usually convenient to conform the type of dependency directly to this +protocol: ```swift -private enum APIClientKey: DependencyKey { +extension APIClient: DependencyKey { static let liveValue = APIClient(/* Construct the "live" API client that actually makes network requests and communicates with the outside world. @@ -30,24 +31,11 @@ private enum APIClientKey: DependencyKey { > need to worry about those values when you are just getting started, and instead can add them > later. See for more information. -Finally, an extension must be made to `DependencyValues` to expose a computed property for the -dependency: - -```swift -extension DependencyValues { - var apiClient: APIClient { - get { self[APIClientKey.self] } - set { self[APIClientKey.self] = newValue } - } -} -``` - -With those few steps completed you can instantly access your API client dependency from any part of -you code base: +With that done you can instantly access your API client dependency from any part of your code base: ```swift final class TodosModel: ObservableObject { - @Dependency(\.apiClient) var apiClient + @Dependency(APIClient.self) var apiClient // ... } ``` @@ -59,7 +47,7 @@ you can override the dependency to return mock data: @MainActor func testFetchUser() async { let model = withDependencies { - $0.apiClient.fetchTodos = { _ in Todo(id: 1, title: "Get milk") } + $0[APIClient.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } } operation: { TodosModel() } @@ -72,26 +60,68 @@ func testFetchUser() async { } ``` -Often times it is not necessary to create a whole new type to conform to `DependencyKey`. If the -dependency you are registering is a type that you own, then you can conform it directly to the -protocol: +It is not always appropriate to conform your dependency directly to the `DependencyKey` protocol, +for example if you have multiple dependency values of the same type, or if it is a type you do not +own. In such cases you can define a separate type that conforms to `DependencyKey` that returns your +dependency as a live value: + +```diff +-extension APIClient: DependencyKey { ++enum APIClientKey: DependencyKey { + static let liveValue = APIClient(/* + Construct the "live" API client that actually makes network + requests and communicates with the outside world. + */) + } +``` -```swift -extension APIClient: DependencyKey { - static let liveValue = APIClient(/* - Construct the "live" API client that actually makes network - requests and communicates with the outside world. - */) -} +And you can access and override your dependency through this key type, instead: + +```diff +-@Dependency(APIClient.self) var apiClient ++@Dependency(APIClientKey.self) var apiClient + let model = withDependencies { +- $0[APIClient.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } ++ $0[APIClientKey.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } + } operation: { + TodosModel() + } +``` + +You can take one additional step to register your dependency value at a particular key path: + +```swift extension DependencyValues { var apiClient: APIClient { - get { self[APIClient.self] } - set { self[APIClient.self] = newValue } + get { self[APIClientKey.self] } + set { self[APIClientKey.self] = newValue } } } ``` -That can save a little bit of boilerplate. +This allows you to access and override the dependency in way similar to SwiftUI environment values, +as a property that is discoverable from autocomplete: + +```diff +-@Dependency(APIClientKey.self) var apiClient ++@Dependency(\.apiClient) var apiClient + + let model = withDependencies { +- $0[APIClientKey.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } ++ $0.apiClient.fetchTodos = { _ in Todo(id: 1, title: "Get milk") } + } operation: { + TodosModel() + } +``` + +And allows you to make the dependency key private: + +```diff +-enum APIClientKey: DependencyKey { ++private enum APIClientKey: Dependency Key { + // ... + } +``` [environment-values-docs]: https://developer.apple.com/documentation/swiftui/environmentvalues From d29128446e63d78791b91e2f955b18164950d6b0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Jan 2024 11:47:32 -0800 Subject: [PATCH 2/4] wip --- .../Articles/RegisteringDependencies.md | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md index 150ae3fa..ab930ce0 100644 --- a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md +++ b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md @@ -60,36 +60,12 @@ func testFetchUser() async { } ``` -It is not always appropriate to conform your dependency directly to the `DependencyKey` protocol, -for example if you have multiple dependency values of the same type, or if it is a type you do not -own. In such cases you can define a separate type that conforms to `DependencyKey` that returns your -dependency as a live value: - -```diff --extension APIClient: DependencyKey { -+enum APIClientKey: DependencyKey { - static let liveValue = APIClient(/* - Construct the "live" API client that actually makes network - requests and communicates with the outside world. - */) - } -``` - -And you can access and override your dependency through this key type, instead: - -```diff --@Dependency(APIClient.self) var apiClient -+@Dependency(APIClientKey.self) var apiClient +## Advanced techniques - let model = withDependencies { -- $0[APIClient.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } -+ $0[APIClientKey.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } - } operation: { - TodosModel() - } -``` +### Dependency key paths -You can take one additional step to register your dependency value at a particular key path: +You can take one additional step to register your dependency value at a particular key path, and +that is by extending `DependencyValues` with a property: ```swift extension DependencyValues { @@ -104,24 +80,48 @@ This allows you to access and override the dependency in way similar to SwiftUI as a property that is discoverable from autocomplete: ```diff --@Dependency(APIClientKey.self) var apiClient +-@Dependency(APIClient.self) var apiClient +@Dependency(\.apiClient) var apiClient let model = withDependencies { -- $0[APIClientKey.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } +- $0[APIClient.self].fetchTodos = { _ in Todo(id: 1, title: "Get milk") } + $0.apiClient.fetchTodos = { _ in Todo(id: 1, title: "Get milk") } } operation: { TodosModel() } ``` -And allows you to make the dependency key private: +Another benefit of this style is the ability to scope a `@Dependency` to a specific property: -```diff --enum APIClientKey: DependencyKey { -+private enum APIClientKey: Dependency Key { - // ... - } +```swift +// This feature only needs the API client's logged-in user +@Dependency(\.apiClient.currentUser) var currentUser +``` + +### Indirect dependency key conformances + +It is not always appropriate to conform your dependency directly to the `DependencyKey` protocol, +for example if it is a type you do not own. In such cases you can define a separate type that +conforms to `DependencyKey`: + +```swift +enum UserDefaultsKey: DependencyKey { + static let liveValue = UserDefaults.standard +} +``` + +You can then access and override your dependency through this key type, instead of the value's type: + +```swift +@Dependency(UserDefaultsKey.self) var userDefaults + +let model = withDependencies { + let defaults = UserDefaults(suiteName: "test-defaults") + defaults.removePersistentDomain(forName: "test-defaults") + $0[UserDefaultsKey.self] = defaults +} operation: { + TodosModel() +} ``` [environment-values-docs]: https://developer.apple.com/documentation/swiftui/environmentvalues From 569c3c586a893460a28342f05e0f8da73d3a8e0a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Jan 2024 11:51:22 -0800 Subject: [PATCH 3/4] wip --- .../Articles/RegisteringDependencies.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md index ab930ce0..a489e4e8 100644 --- a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md +++ b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md @@ -124,4 +124,18 @@ let model = withDependencies { } ``` +If you extend dependency values with a dedicated key path, you can even make this key private: + +```diff +-enum UserDefaultsKey: DependencyKey { /* ... */ } ++private enum UserDefaultsKey: DependencyKey { /* ... */ } ++ ++extension DependencyValues { ++ var userDefaults: APIClient { ++ get { self[UserDefaultsKey.self] } ++ set { self[UserDefaultsKey.self] = newValue } ++ } ++} +``` + [environment-values-docs]: https://developer.apple.com/documentation/swiftui/environmentvalues From 24f1da20446c3917a97b015a0adacb0ff44fe168 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Jan 2024 11:57:13 -0800 Subject: [PATCH 4/4] wip --- .../Documentation.docc/Articles/RegisteringDependencies.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md index a489e4e8..49cae969 100644 --- a/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md +++ b/Sources/Dependencies/Documentation.docc/Articles/RegisteringDependencies.md @@ -91,10 +91,10 @@ as a property that is discoverable from autocomplete: } ``` -Another benefit of this style is the ability to scope a `@Dependency` to a specific property: +Another benefit of this style is the ability to scope a `@Dependency` to a specific sub-property: ```swift -// This feature only needs the API client's logged-in user +// This feature only needs to access the API client's logged-in user @Dependency(\.apiClient.currentUser) var currentUser ```