From e9e82b5302025092ab8358e794f89a0f0397dd9d Mon Sep 17 00:00:00 2001 From: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> Date: Tue, 10 Jan 2023 14:35:56 -0300 Subject: [PATCH] Capture current dependencies when escaping (#11) * Capture current dependencies when escaping * Merge with current * wip * Capture escaped dependencies and restore them without merging logic. * wip * docs * fix test Co-authored-by: Brandon Williams --- Sources/Dependencies/WithDependencies.swift | 31 +++++++++++++++---- .../DependencyValuesTests.swift | 8 ++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Sources/Dependencies/WithDependencies.swift b/Sources/Dependencies/WithDependencies.swift index 307b539c..c4d32736 100644 --- a/Sources/Dependencies/WithDependencies.swift +++ b/Sources/Dependencies/WithDependencies.swift @@ -309,17 +309,35 @@ public func withDependencies( /// withEscapedDependencies { dependencies in /// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { /// dependencies.yield { -/// // All code in here will use dependencies at the time of -/// // calling DependencyValues.escape. +/// // All code in here will use dependencies at the time of calling withEscapedDependencies. /// } /// } /// } /// ``` /// /// As a general rule, you should surround _all_ escaping code that may access dependencies with -/// this helper. Otherwise you run the risk of the escaped code using the wrong dependencies. But, -/// you should also try your hardest to keep your code in the structured world using Swift's tools -/// of structured concurrency, and should avoid using escaping closures. +/// this helper, and you should use ``DependencyValues/Continuation/yield(_:)-42ttb`` _immediately_ +/// inside the escaping closure. Otherwise you run the risk of the escaped code using the wrong +/// dependencies. But, you should also try your hardest to keep your code in the structured world +/// using Swift's tools of structured concurrency, and should avoid using escaping closures. +/// +/// If you need to further override dependencies in the escaped closure, do so inside the +/// ``DependencyValues/Continuation/yield(_:)-42ttb`` and not outside: +/// +/// ```swift +/// withEscapedDependencies { dependencies in +/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +/// dependencies.yield { +/// withDependencies { +/// $0.apiClient = .mock +/// } operation: { +/// // All code in here will use dependencies at the time of calling +/// // withEscapedDependencies except the API client will be mocked. +/// } +/// } +/// } +/// } +/// ``` /// /// - Parameter operation: A closure that takes a ``DependencyValues/Continuation`` value for /// propagating dependencies past an escaping closure boundary. @@ -346,13 +364,14 @@ extension DependencyValues { /// /// See the docs of ``withEscapedDependencies(_:)-5xvi3`` for more information. public struct Continuation: Sendable { - @Dependency(\.self) private var dependencies + let dependencies = DependencyValues._current /// Access the propagated dependencies in an escaping context. /// /// See the docs of ``withEscapedDependencies(_:)-5xvi3`` for more information. /// - Parameter operation: A closure which will have access to the propagated dependencies. public func yield(_ operation: () throws -> R) rethrows -> R { + // TODO: Should `yield` be renamed to `restore`? try withDependencies { $0 = self.dependencies } operation: { diff --git a/Tests/DependenciesTests/DependencyValuesTests.swift b/Tests/DependenciesTests/DependencyValuesTests.swift index 9c9d1f86..34eae0fc 100644 --- a/Tests/DependenciesTests/DependencyValuesTests.swift +++ b/Tests/DependenciesTests/DependencyValuesTests.swift @@ -396,10 +396,10 @@ final class DependencyValuesTests: XCTestCase { func doSomething(expectation: XCTestExpectation) { withEscapedDependencies { continuation in DispatchQueue.main.async { - withDependencies { - $0.fullDependency.value = 999 - } operation: { - continuation.yield { + continuation.yield { + withDependencies { + $0.fullDependency.value = 999 + } operation: { self.value = self.fullDependency.value expectation.fulfill() }