Skip to content

Commit

Permalink
Capture current dependencies when escaping (#11)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
tgrapperon and mbrandonw authored Jan 10, 2023
1 parent 0702d62 commit e9e82b5
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 10 deletions.
31 changes: 25 additions & 6 deletions Sources/Dependencies/WithDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,17 +309,35 @@ public func withDependencies<Model: AnyObject, R>(
/// 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.
Expand All @@ -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<R>(_ operation: () throws -> R) rethrows -> R {
// TODO: Should `yield` be renamed to `restore`?
try withDependencies {
$0 = self.dependencies
} operation: {
Expand Down
8 changes: 4 additions & 4 deletions Tests/DependenciesTests/DependencyValuesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down

0 comments on commit e9e82b5

Please sign in to comment.