Skip to content

Commit 486b073

Browse files
authored
Update EnvironmentValues (#268)
* Update EnvironmentValues implementation * Update Environment test case * Update EnvironmentKey documentation * Fix test case issue
1 parent b1432b7 commit 486b073

File tree

7 files changed

+346
-19
lines changed

7 files changed

+346
-19
lines changed

Sources/OpenSwiftUI/Util/OpenSwiftUIGlue.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ final public class OpenSwiftUIGlue2: CoreGlue2 {
9797
}
9898

9999
override public final func configureDefaultEnvironment(_: inout EnvironmentValues) {
100+
// TODO
100101
}
101102

102103
override public final func makeRootView(base: AnyView, rootFocusScope: Namespace.ID) -> AnyView {

Sources/OpenSwiftUICore/Data/Environment/EnvironmentKey.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ public protocol EnvironmentKey {
7070
/// The default value for the environment key.
7171
static var defaultValue: Value { get }
7272

73+
/// Compares two values of the environment key's associated type.
74+
///
75+
/// This function determines if two values of this environment key's
76+
/// associated type should be considered equal. OpenSwiftUI uses this
77+
/// to determine when environment changes should trigger view updates.
78+
///
79+
/// - Parameters:
80+
/// - lhs: The first value to compare.
81+
/// - rhs: The second value to compare.
82+
/// - Returns: `true` if the values are considered equal; otherwise, `false`.
83+
///
84+
/// The default implementation uses general value comparison for non-Equatable types.
85+
/// Types conforming to `Equatable` receive an optimized implementation that uses
86+
/// the `==` operator.
7387
static func _valuesEqual(_ lhs: Self.Value, _ rhs: Self.Value) -> Bool
7488
}
7589

@@ -85,7 +99,42 @@ extension EnvironmentKey where Value: Equatable {
8599
}
86100
}
87101

102+
/// A protocol that defines environment keys derived from other environment values.
103+
///
104+
/// Unlike standard `EnvironmentKey` that stores values directly, a `DerivedEnvironmentKey`
105+
/// calculates its value dynamically based on other environment values.
106+
///
107+
/// To implement a derived environment key:
108+
/// 1. Create a type that conforms to `DerivedEnvironmentKey`
109+
/// 2. Specify the `Value` type that must conform to `Equatable`
110+
/// 3. Implement the `value(in:)` method to compute the derived value
111+
///
112+
/// Example:
113+
///
114+
/// private struct MyDerivedKey: DerivedEnvironmentKey {
115+
/// typealias Value = String
116+
///
117+
/// static func value(in values: EnvironmentValues) -> String {
118+
/// return "\(values.someOtherValue) derived"
119+
/// }
120+
/// }
121+
///
122+
/// Then extend `EnvironmentValues` to access your derived value:
123+
///
124+
/// extension EnvironmentValues {
125+
/// var myDerivedValue: String {
126+
/// self[MyDerivedKey.self]
127+
/// }
128+
/// }
129+
///
88130
package protocol DerivedEnvironmentKey {
131+
/// The associated type representing the type of the environment key's
132+
/// value.
89133
associatedtype Value: Equatable
134+
135+
/// Calculates the derived value based on the current environment values.
136+
///
137+
/// - Parameter values: The current environment values.
138+
/// - Returns: The derived value for this key.
90139
static func value(in: EnvironmentValues) -> Value
91140
}

Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues.swift

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// OpenSwiftUICore
44
//
55
// Audited for iOS 18.0
6-
// Status: TODO
6+
// Status: Complete
77
// ID: 83E729E7BD00420AB79EFD8DF557072A (SwiftUI)
88
// ID: 0CBA6217BE011883F496E97230B6CF8F (SwiftUICore)
99

@@ -87,28 +87,92 @@ public struct EnvironmentValues: CustomStringConvertible {
8787
public init() {
8888
_plist = PropertyList()
8989
tracker = nil
90-
// TODO: CoreGlue.shared
90+
CoreGlue2.shared.configureDefaultEnvironment(&self)
9191
}
9292

93+
/// Creates an environment values instance with the specified property list.
94+
///
95+
/// - Parameter plist: The property list to initialize the environment values with.
9396
package init(_ plist: PropertyList) {
9497
_plist = plist
9598
tracker = nil
9699
}
97100

101+
/// Creates an environment values instance with the specified property list and tracker.
102+
///
103+
/// - Parameters:
104+
/// - plist: The property list to initialize the environment values with.
105+
/// - tracker: The tracker to monitor property list changes.
98106
package init(_ plist: PropertyList, tracker: PropertyList.Tracker) {
99-
// FIXME
107+
tracker.initializeValues(from: plist)
100108
self._plist = plist
101109
self.tracker = tracker
102110
}
103111

104-
// FIXME
112+
/// The underlying property list that stores environment values.
105113
package var plist: PropertyList {
106-
get { _plist }
114+
get {
115+
_plist
116+
}
107117
set {
118+
guard _plist.id != newValue.id else {
119+
return
120+
}
121+
if let tracker {
122+
tracker.invalidateAllValues(from: _plist, to: newValue)
123+
}
108124
_plist = newValue
109125
}
110126
}
111127

128+
/// Returns a new environment values instance without a tracker.
129+
///
130+
/// - Returns: An environment values instance with the same property list but no tracker.
131+
package func removingTracker() -> EnvironmentValues {
132+
EnvironmentValues(_plist)
133+
}
134+
135+
/// Adds dependencies from another tracker to this environment's tracker.
136+
///
137+
/// - Parameter other: The tracker whose dependencies should be added.
138+
package func addDependencies(from other: PropertyList.Tracker) {
139+
guard let tracker else { return }
140+
tracker.formUnion(other)
141+
}
142+
143+
/// Retrieves a value using a secondary lookup mechanism.
144+
///
145+
/// - Parameter key: The lookup key type.
146+
/// - Returns: The value associated with the key's primary type.
147+
package func valueWithSecondaryLookup<Lookup>(_ key: Lookup.Type) -> Lookup.Primary.Value where Lookup: PropertyKeyLookup {
148+
if let tracker {
149+
tracker.valueWithSecondaryLookup(_plist, secondaryLookupHandler: key)
150+
} else {
151+
_plist.valueWithSecondaryLookup(key)
152+
}
153+
}
154+
155+
/// Sets a value for the specified environment key.
156+
///
157+
/// - Parameters:
158+
/// - value: The value to set.
159+
/// - key: The environment key type.
160+
package mutating func setValue<K>(_ value: K.Value, for key: K.Type) where K: EnvironmentKey {
161+
_set(value, for: key)
162+
}
163+
164+
private mutating func _set<K>(_ value: K.Value, for key: K.Type) where K: EnvironmentKey {
165+
let oldPlist = _plist
166+
_plist[EnvironmentPropertyKey<K>.self] = value
167+
if let tracker {
168+
tracker.invalidateValue(
169+
for: EnvironmentPropertyKey<K>.self,
170+
from: oldPlist,
171+
to: plist
172+
)
173+
}
174+
}
175+
112176
/// Accesses the environment value associated with a custom key.
113177
///
114178
/// Create custom environment values by defining a key
@@ -148,17 +212,17 @@ public struct EnvironmentValues: CustomStringConvertible {
148212
}
149213
}
150214
set {
151-
_plist[EnvironmentPropertyKey<K>.self] = newValue
152-
if let tracker {
153-
tracker.invalidateValue(
154-
for: EnvironmentPropertyKey<K>.self,
155-
from: _plist,
156-
to: _plist
157-
)
158-
}
215+
setValue(newValue, for: key)
159216
}
160217
}
161218

219+
/// Accesses the environment value associated with a derived environment key.
220+
///
221+
/// Derived environment keys calculate their values based on other environment values.
222+
/// This subscript provides read-only access to these derived values.
223+
///
224+
/// - Parameter key: The derived environment key type.
225+
/// - Returns: The value associated with the key.
162226
package subscript<K>(key: K.Type) -> K.Value where K: DerivedEnvironmentKey {
163227
if let tracker {
164228
tracker.derivedValue(_plist, for: DerivedEnvironmentPropertyKey<K>.self)
@@ -175,12 +239,25 @@ public struct EnvironmentValues: CustomStringConvertible {
175239
@available(*, unavailable)
176240
extension EnvironmentValues: Sendable {}
177241

178-
private struct EnvironmentPropertyKey<EnvKey: EnvironmentKey>: PropertyKey {
179-
static var defaultValue: EnvKey.Value { EnvKey.defaultValue }
242+
/// A property key that provides access to environment values.
243+
///
244+
/// This type bridges between the `EnvironmentKey` protocol and the internal `PropertyKey` system.
245+
private struct EnvironmentPropertyKey<Key>: PropertyKey where Key: EnvironmentKey {
246+
/// The default value for this property key, obtained from the environment key.
247+
static var defaultValue: Key.Value {
248+
Key.defaultValue
249+
}
180250
}
181251

182-
private struct DerivedEnvironmentPropertyKey<EnvKey: DerivedEnvironmentKey>: DerivedPropertyKey {
183-
static func value(in plist: PropertyList) -> EnvKey.Value {
184-
EnvKey.value(in: EnvironmentValues(plist))
252+
/// A property key that provides access to derived environment values.
253+
///
254+
/// This type bridges between the `DerivedEnvironmentKey` protocol and the internal `DerivedPropertyKey` system.
255+
private struct DerivedEnvironmentPropertyKey<Key>: DerivedPropertyKey where Key: DerivedEnvironmentKey {
256+
/// Calculates the derived value based on the current environment.
257+
///
258+
/// - Parameter plist: The property list containing the current environment state.
259+
/// - Returns: The calculated value for this derived key.
260+
static func value(in plist: PropertyList) -> Key.Value {
261+
Key.value(in: EnvironmentValues(plist))
185262
}
186263
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// EnvironmentKey+Test.swift
3+
// OpenSwiftUITestsSupport
4+
5+
package import OpenSwiftUI
6+
7+
package struct StringEnvironmentKey: EnvironmentKey {
8+
package static var defaultValue: String { "" }
9+
}
10+
11+
package struct IntEnvironmentKey: EnvironmentKey {
12+
package static var defaultValue: Int { 0 }
13+
}
14+
15+
package struct BoolEnvironmentKey: EnvironmentKey {
16+
package static var defaultValue: Bool { false }
17+
}
18+
19+
package struct DerivedStringEnvironmentKey: DerivedEnvironmentKey {
20+
package static func value(in environment: EnvironmentValues) -> String {
21+
"d:\(environment[StringEnvironmentKey.self])"
22+
}
23+
}
24+
25+
package struct OptionalStringEnvironmentKey: EnvironmentKey {
26+
package static let defaultValue: String? = nil
27+
}
28+
29+
package struct CustomStructEnvironmentKey: EnvironmentKey {
30+
package struct CustomStruct {
31+
package let value: Int
32+
33+
package init(value: Int) {
34+
self.value = value
35+
}
36+
}
37+
38+
package static let defaultValue = CustomStruct(value: 100)
39+
}

Sources/OpenSwiftUITestsSupport/Data/PropertyList+Test.swift renamed to Sources/OpenSwiftUITestsSupport/Data/PropertyListKey+Test.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// PropertyList+Test.swift
2+
// PropertyListKey+Test.swift
33
// OpenSwiftUITestsSupport
44

55
package import OpenSwiftUI
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// EnvironmentKeyTests.swift
3+
// OpenSwiftUICoreTests
4+
5+
import OpenSwiftUICore
6+
import OpenSwiftUITestsSupport
7+
import Testing
8+
9+
struct EnvironmentKeyTests {
10+
// MARK: - Default Value Tests
11+
12+
@Test
13+
func defaultValues() {
14+
#expect(StringEnvironmentKey.defaultValue == "")
15+
#expect(IntEnvironmentKey.defaultValue == 0)
16+
#expect(OptionalStringEnvironmentKey.defaultValue == nil)
17+
#expect(CustomStructEnvironmentKey.defaultValue.value == 100)
18+
}
19+
20+
// MARK: - Value Equality Tests
21+
22+
@Test
23+
func equatableValuesComparison() {
24+
#expect(StringEnvironmentKey._valuesEqual("test", "test") == true)
25+
#expect(StringEnvironmentKey._valuesEqual("test", "different") == false)
26+
27+
#expect(IntEnvironmentKey._valuesEqual(42, 42) == true)
28+
#expect(IntEnvironmentKey._valuesEqual(42, 43) == false)
29+
30+
#expect(OptionalStringEnvironmentKey._valuesEqual(nil, nil) == true)
31+
#expect(OptionalStringEnvironmentKey._valuesEqual("test", "test") == true)
32+
#expect(OptionalStringEnvironmentKey._valuesEqual("test", nil) == false)
33+
#expect(OptionalStringEnvironmentKey._valuesEqual(nil, "test") == false)
34+
}
35+
36+
#if canImport(Darwin) // FIXME: compareValues is not implemented on OG yet
37+
@Test
38+
func nonEquatableValuesComparison() {
39+
let struct1 = CustomStructEnvironmentKey.CustomStruct(value: 100)
40+
let struct2 = CustomStructEnvironmentKey.CustomStruct(value: 100)
41+
let struct3 = CustomStructEnvironmentKey.CustomStruct(value: 200)
42+
43+
#expect(CustomStructEnvironmentKey._valuesEqual(struct1, struct2) == true)
44+
#expect(CustomStructEnvironmentKey._valuesEqual(struct1, struct3) == false)
45+
}
46+
#endif
47+
48+
// MARK: - DerivedEnvironmentKey Tests
49+
50+
@Test
51+
func derivedEnvironmentKey() {
52+
var environment = EnvironmentValues()
53+
environment[StringEnvironmentKey.self] = "test value"
54+
let derived = DerivedStringEnvironmentKey.value(in: environment)
55+
#expect(derived == "d:test value")
56+
}
57+
}

0 commit comments

Comments
 (0)