diff --git a/.gitignore b/.gitignore
index 57e7f7f..78abd26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/*.xcodeproj
xcuserdata/
Package.resolved
+.swiftpm
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 919434a..0000000
--- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableEnvironment.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ComposableEnvironment.xcscheme
deleted file mode 100644
index 929f38a..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableEnvironment.xcscheme
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Sources/ComposableEnvironment/ComposableEnvironment.swift b/Sources/ComposableEnvironment/ComposableEnvironment.swift
index 1fbdab5..02bc1e8 100644
--- a/Sources/ComposableEnvironment/ComposableEnvironment.swift
+++ b/Sources/ComposableEnvironment/ComposableEnvironment.swift
@@ -35,7 +35,15 @@ open class ComposableEnvironment {
/// individual dependencies. These values ill propagate to each child``DerivedEnvironment`` as
/// well as their own children ``DerivedEnvironment``.
public required init() {}
-
+
+ var _dependencies = _ComposableDependencies()
+
+ @discardableResult
+ func connected(to env: ComposableEnvironment) -> Self {
+ self._dependencies.context = env._dependencies
+ return self
+ }
+
var dependencies: ComposableDependencies = .init() {
didSet {
// This will make any child refetch its upstream dependencies when accessed.
@@ -46,7 +54,7 @@ open class ComposableEnvironment {
var upToDateDerivedEnvironments: NSHashTable = .weakObjects()
@discardableResult
- func updatingFromParentIfNeeded(_ parent: ComposableEnvironment) -> Self {
+ public func updatingFromParentIfNeeded(_ parent: ComposableEnvironment) -> Self {
if !parent.upToDateDerivedEnvironments.contains(self) {
// The following line updates the `environment`'s dependencies, invalidating its children
// dependencies when it mutates its own `dependencies` property as a side effect.
@@ -77,23 +85,23 @@ open class ComposableEnvironment {
/// .with(\.mainQueue, .main)
/// ```
@discardableResult
- public func with(_ keyPath: WritableKeyPath, _ value: V) -> Self {
- dependencies[keyPath: keyPath] = value
+ public func with(_ keyPath: WritableKeyPath<_ComposableDependencies, V>, _ value: V) -> Self {
+ _dependencies[keyPath: keyPath] = value
return self
}
/// A read-write subcript to directly access a dependency from its `KeyPath` in
/// ``ComposableDependencies``.
- public subscript(keyPath: WritableKeyPath) -> Value {
- get { dependencies[keyPath: keyPath] }
- set { dependencies[keyPath: keyPath] = newValue }
+ public subscript(keyPath: WritableKeyPath<_ComposableDependencies, Value>) -> Value {
+ get { _dependencies[keyPath: keyPath] }
+ set { _dependencies[keyPath: keyPath] = newValue }
}
/// A read-only subcript to directly access a dependency from ``ComposableDependencies``.
/// - Remark: This direct access can't be used to set a dependency, as it will try to go through
/// the setter part of a ``Dependency`` property wrapper, which is not allowed yet. You can use
/// ``with(_:_:)`` or ``subscript(_:)`` instead.
- public subscript(dynamicMember keyPath: KeyPath) -> Value {
- get { dependencies[keyPath: keyPath] }
+ public subscript(dynamicMember keyPath: KeyPath<_ComposableDependencies, Value>) -> Value {
+ get { _dependencies[keyPath: keyPath] }
}
}
diff --git a/Sources/ComposableEnvironment/Dependencies.swift b/Sources/ComposableEnvironment/Dependencies.swift
index 141f1fe..78cb3af 100644
--- a/Sources/ComposableEnvironment/Dependencies.swift
+++ b/Sources/ComposableEnvironment/Dependencies.swift
@@ -69,3 +69,13 @@ public struct ComposableDependencies {
}
}
}
+
+public final class _ComposableDependencies {
+ weak var context: _ComposableDependencies?
+ var values: [ObjectIdentifier: Any] = [:]
+
+ public subscript(_ key: T.Type) -> T.Value where T: DependencyKey {
+ get { values[ObjectIdentifier(key)] as? T.Value ?? context?[key] ?? key.defaultValue }
+ set { values[ObjectIdentifier(key)] = newValue }
+ }
+}
diff --git a/Sources/ComposableEnvironment/Dependency.swift b/Sources/ComposableEnvironment/Dependency.swift
index 3632344..28b6459 100644
--- a/Sources/ComposableEnvironment/Dependency.swift
+++ b/Sources/ComposableEnvironment/Dependency.swift
@@ -54,3 +54,60 @@ public struct Dependency {
set { fatalError() }
}
}
+
+/// Use this property wrapper to declare depencies in a ``ComposableEnvironment`` subclass.
+///
+/// You reference the dependency by its `KeyPath` originating from ``ComposableDependencies``, and
+/// you declare its name in the local environment. The dependency should not be instantiated, as it
+/// is either inherited from a ``ComposableEnvironment`` parent, or installed with
+/// ``ComposableEnvironment/with(_:_:)``.
+///
+/// For example, if the dependency is declared as:
+/// ```swift
+/// extension ComposableDependencies {
+/// var uuidGenerator: () -> UUID {
+/// get { self[UUIDGeneratorKey.self] }
+/// set { self[UUIDGeneratorKey.self] = newValue }
+/// }
+/// },
+/// ```
+/// you can install it in `LocalEnvironment` like:
+/// ```swift
+/// class LocalEnvironment: ComposableEnvironment {
+/// @Dependency(\.uuidGenerator) var uuid
+/// }
+/// ```
+/// This exposes a `var uuid: () -> UUID` read-only property in the `LocalEnvironment`. This
+/// property can then be used as any vanilla dependency.
+@propertyWrapper
+public struct _Dependency {
+ /// Alternative to ``wrappedValue`` with access to the enclosing instance.
+ public static subscript(
+ _enclosingInstance instance: EnclosingSelf,
+ wrapped wrappedKeyPath: ReferenceWritableKeyPath,
+ storage storageKeyPath: ReferenceWritableKeyPath
+ ) -> Value {
+ get {
+ let wrapper = instance[keyPath: storageKeyPath]
+ let keyPath = wrapper.keyPath
+ let value = instance._dependencies[keyPath: keyPath]
+ return value
+ }
+ set {
+ fatalError("@Dependency are read-only in their ComposableEnvironment")
+ }
+ }
+
+ var keyPath: KeyPath<_ComposableDependencies, Value>
+
+ /// See ``Dependency`` discussion
+ public init(_ keyPath: KeyPath<_ComposableDependencies, Value>) {
+ self.keyPath = keyPath
+ }
+
+ @available(*, unavailable, message: "@Dependency should be used in a ComposableEnvironment class.")
+ public var wrappedValue: Value {
+ get { fatalError() }
+ set { fatalError() }
+ }
+}
diff --git a/Sources/ComposableEnvironment/DerivedEnvironment.swift b/Sources/ComposableEnvironment/DerivedEnvironment.swift
index 68da536..69f59e7 100644
--- a/Sources/ComposableEnvironment/DerivedEnvironment.swift
+++ b/Sources/ComposableEnvironment/DerivedEnvironment.swift
@@ -48,3 +48,54 @@ public final class DerivedEnvironment where Value: ComposableEnvironment
set { fatalError() }
}
}
+
+/// Use this property wrapper to declare child ``ComposableEnvironment`` in a
+/// ``ComposableEnvironment`` subclass.
+///
+/// You only need to specify the subclass used and its name. You don't need to instantiate the
+/// subclass. For example, if `ChildEnvironment` is a ``ComposableEnvironment`` subclass, you can
+/// install a representant in `ParentEnvironment` as:
+/// ```swift
+/// class ParentEnvironment: ComposableEnvironment {
+/// @DerivedEnvironment var child
+/// }.
+/// ```
+/// This exposes a `var child: ChildEnvironment` read-only property in the `ParentEnvironment`.
+/// This child environment inherits the current dependencies of all its ancestor. They can be
+/// exposed using the ``Dependency`` property wrapper.
+@propertyWrapper
+public final class _DerivedEnvironment where Value: ComposableEnvironment {
+ /// Alternative to ``wrappedValue`` with access to the enclosing instance.
+ public static subscript(
+ _enclosingInstance instance: EnclosingSelf,
+ wrapped wrappedKeyPath: ReferenceWritableKeyPath,
+ storage storageKeyPath: ReferenceWritableKeyPath
+ ) -> Value {
+ get {
+ instance[keyPath: storageKeyPath]
+ .environment
+ .connected(to: instance)
+ }
+ set {
+ fatalError("@DerivedEnvironments are read-only in their parent")
+ }
+ }
+
+ var environment: Value
+
+ /// See ``DerivedEnvironment`` discussion
+ public init(wrappedValue: Value) {
+ self.environment = wrappedValue
+ }
+
+ /// See ``DerivedEnvironment`` discussion
+ public init() {
+ self.environment = Value()
+ }
+
+ @available(*, unavailable, message: "@DerivedEnvironment should be used in a ComposableEnvironment class.")
+ public var wrappedValue: Value {
+ get { fatalError() }
+ set { fatalError() }
+ }
+}
diff --git a/Sources/ComposableEnvironment/Reducer+ComposableEnvironment.swift b/Sources/ComposableEnvironment/Reducer+ComposableEnvironment.swift
index d92e5cc..0ecd4c3 100644
--- a/Sources/ComposableEnvironment/Reducer+ComposableEnvironment.swift
+++ b/Sources/ComposableEnvironment/Reducer+ComposableEnvironment.swift
@@ -25,7 +25,7 @@ public extension Reducer where Environment: ComposableEnvironment {
return pullback(
state: toLocalState,
action: toLocalAction,
- environment: local.updatingFromParentIfNeeded
+ environment: local.connected
)
}
@@ -58,7 +58,7 @@ public extension Reducer where Environment: ComposableEnvironment {
return pullback(
state: toLocalState,
action: toLocalAction,
- environment: local.updatingFromParentIfNeeded,
+ environment: local.connected,
breakpointOnNil: breakpointOnNil
)
}
@@ -92,7 +92,7 @@ public extension Reducer where Environment: ComposableEnvironment {
return forEach(
state: toLocalState,
action: toLocalAction,
- environment: local.updatingFromParentIfNeeded,
+ environment: local.connected,
breakpointOnNil: breakpointOnNil
)
}
@@ -125,7 +125,7 @@ public extension Reducer where Environment: ComposableEnvironment {
return forEach(
state: toLocalState,
action: toLocalAction,
- environment: local.updatingFromParentIfNeeded,
+ environment: local.connected,
breakpointOnNil: breakpointOnNil
)
}
diff --git a/Tests/ComposableEnvironmentTests/ComposableEnvironmentTests.swift b/Tests/ComposableEnvironmentTests/ComposableEnvironmentTests.swift
index 7a2016a..ff6b776 100644
--- a/Tests/ComposableEnvironmentTests/ComposableEnvironmentTests.swift
+++ b/Tests/ComposableEnvironmentTests/ComposableEnvironmentTests.swift
@@ -5,7 +5,7 @@ fileprivate struct IntKey: DependencyKey {
static var defaultValue: Int { 1 }
}
-fileprivate extension ComposableDependencies {
+fileprivate extension _ComposableDependencies {
var int: Int {
get { self[IntKey.self] }
set { self[IntKey.self] = newValue }
@@ -13,21 +13,21 @@ fileprivate extension ComposableDependencies {
}
final class ComposableEnvironmentTests: XCTestCase {
- func testDependency() {
+ func test_Dependency() {
class Env: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
}
let env = Env()
XCTAssertEqual(env.int, 1)
}
- func testDependencyPropagation() {
+ func test_DependencyPropagation() {
class Parent: ComposableEnvironment {
- @Dependency(\.int) var int
- @DerivedEnvironment var child
+ @_Dependency(\.int) var int
+ @_DerivedEnvironment var child
}
class Child: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
}
let parent = Parent()
XCTAssertEqual(parent.child.int, 1)
@@ -37,14 +37,14 @@ final class ComposableEnvironmentTests: XCTestCase {
XCTAssertEqual(parentWith2.child.int, 2)
}
- func testDependencyOverride() {
+ func test_DependencyOverride() {
class Parent: ComposableEnvironment {
- @Dependency(\.int) var int
- @DerivedEnvironment var child
- @DerivedEnvironment var sibling = Child().with(\.int, 3)
+ @_Dependency(\.int) var int
+ @_DerivedEnvironment var child
+ @_DerivedEnvironment var sibling = Child().with(\.int, 3)
}
class Child: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
}
let parent = Parent().with(\.int, 2)
@@ -55,12 +55,12 @@ final class ComposableEnvironmentTests: XCTestCase {
func testDerivedWithProperties() {
class Parent: ComposableEnvironment {
- @Dependency(\.int) var int
- @DerivedEnvironment var child
- @DerivedEnvironment var sibling = Child(otherInt: 5).with(\.int, 3)
+ @_Dependency(\.int) var int
+ @_DerivedEnvironment var child
+ @_DerivedEnvironment var sibling = Child(otherInt: 5).with(\.int, 3)
}
final class Child: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
var otherInt: Int = 4
required init() {}
init(otherInt: Int) {
@@ -79,24 +79,24 @@ final class ComposableEnvironmentTests: XCTestCase {
func testLongChainsPropagation() {
class Parent: ComposableEnvironment {
- @Dependency(\.int) var int
- @DerivedEnvironment var c1
+ @_Dependency(\.int) var int
+ @_DerivedEnvironment var c1
}
final class C1: ComposableEnvironment {
- @DerivedEnvironment var c2
+ @_DerivedEnvironment var c2
}
final class C2: ComposableEnvironment {
- @DerivedEnvironment var c3
+ @_DerivedEnvironment var c3
}
final class C3: ComposableEnvironment {
- @DerivedEnvironment var c4
- @Dependency(\.int) var int
+ @_DerivedEnvironment var c4
+ @_Dependency(\.int) var int
}
final class C4: ComposableEnvironment {
- @DerivedEnvironment var c5
+ @_DerivedEnvironment var c5
}
final class C5: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
}
let parent = Parent().with(\.int, 4)
XCTAssertEqual(parent.c1.c2.c3.c4.c5.int, 4)
@@ -105,24 +105,24 @@ final class ComposableEnvironmentTests: XCTestCase {
func testModifyingDependenciesOncePrimed() {
class Parent: ComposableEnvironment {
- @Dependency(\.int) var int
- @DerivedEnvironment var c1
+ @_Dependency(\.int) var int
+ @_DerivedEnvironment var c1
}
final class C1: ComposableEnvironment {
- @DerivedEnvironment var c2
+ @_DerivedEnvironment var c2
}
final class C2: ComposableEnvironment {
- @DerivedEnvironment var c3
+ @_DerivedEnvironment var c3
}
final class C3: ComposableEnvironment {
- @DerivedEnvironment var c4
- @Dependency(\.int) var int
+ @_DerivedEnvironment var c4
+ @_Dependency(\.int) var int
}
final class C4: ComposableEnvironment {
- @DerivedEnvironment var c5
+ @_DerivedEnvironment var c5
}
final class C5: ComposableEnvironment {
- @Dependency(\.int) var int
+ @_Dependency(\.int) var int
}
let parent = Parent().with(\.int, 4)
XCTAssertEqual(parent.c1.c2.c3.int, 4)
diff --git a/Tests/ComposableEnvironmentTests/ReducerComposableEnvironmentTests.swift b/Tests/ComposableEnvironmentTests/ReducerComposableEnvironmentTests.swift
index 2a8474e..004c222 100644
--- a/Tests/ComposableEnvironmentTests/ReducerComposableEnvironmentTests.swift
+++ b/Tests/ComposableEnvironmentTests/ReducerComposableEnvironmentTests.swift
@@ -6,7 +6,7 @@ fileprivate struct IntKey: DependencyKey {
static var defaultValue: Int { 1 }
}
-fileprivate extension ComposableDependencies {
+fileprivate extension _ComposableDependencies {
var int: Int {
get { self[IntKey.self] }
set { self[IntKey.self] = newValue }
@@ -175,10 +175,10 @@ final class ReducerAdditionsTests: XCTestCase {
func testComposableAutoComposableComposableBridging() {
class Third: ComposableEnvironment {
- @Dependency(\.int) var integer
+ @_Dependency(\.int) var integer
}
class Second: ComposableEnvironment {
- @DerivedEnvironment var third
+ @_DerivedEnvironment var third
}
class First: ComposableEnvironment {}
@@ -214,7 +214,7 @@ final class ReducerAdditionsTests: XCTestCase {
class Third: ComposableEnvironment { }
class Second: ComposableEnvironment { }
class First: ComposableEnvironment {
- @DerivedEnvironment var second
+ @_DerivedEnvironment var second
}
enum Action {