Skip to content

Commit b069bf8

Browse files
authored
Update Environment implementation (#648)
1 parent d2af991 commit b069bf8

File tree

1 file changed

+87
-34
lines changed

1 file changed

+87
-34
lines changed

Sources/OpenSwiftUICore/Data/Environment/Environment.swift

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//
22
// Environment.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
5-
// Audited for 3.5.2
5+
// Audited for 6.5.4
66
// Status: Complete
7-
// ID: 7B48F30970137591804EEB8D0D309152
7+
// ID: 7B48F30970137591804EEB8D0D309152 (SwiftUI)
8+
// ID: 24E0E088473ED74681D096110CC5FC9A (SwiftUICore)
89

910
import OpenAttributeGraphShims
1011
#if OPENSWIFTUI_SWIFT_LOG
@@ -13,6 +14,8 @@ public import Logging
1314
public import os
1415
#endif
1516

17+
// MARK: - Environment
18+
1619
/// A property wrapper that reads a value from a view's environment.
1720
///
1821
/// Use the `Environment` property wrapper to read a value
@@ -46,7 +49,7 @@ public import os
4649
///
4750
/// For the complete list of environment values provided by OpenSwiftUI, see the
4851
/// properties of the ``EnvironmentValues`` structure. For information about
49-
/// creating custom environment values, see the ``EnvironmentKey`` protocol.
52+
/// creating custom environment values, see the ``Entry()`` macro.
5053
///
5154
/// ### Get an observable object
5255
///
@@ -57,7 +60,7 @@ public import os
5760
/// the object itself or a key path.
5861
///
5962
/// To set the object in the environment using the object itself, use the
60-
/// ``View/environment(_:)-4516h`` modifier:
63+
/// ``View/environment(_:)`` modifier:
6164
///
6265
/// @Observable
6366
/// class Library {
@@ -94,7 +97,7 @@ public import os
9497
/// By default, reading an object from the environment returns a non-optional
9598
/// object when using the object type as the key. This default behavior assumes
9699
/// that a view in the current hierarchy previously stored a non-optional
97-
/// instance of the type using the ``View/environment(_:)-4516h`` modifier. If
100+
/// instance of the type using the ``View/environment(_:)`` modifier. If
98101
/// a view attempts to retrieve an object using its type and that object isn't
99102
/// in the environment, OpenSwiftUI throws an exception.
100103
///
@@ -141,18 +144,25 @@ public import os
141144
/// }
142145
/// }
143146
///
147+
@available(OpenSwiftUI_v1_0, *)
144148
@frozen
145149
@propertyWrapper
146150
public struct Environment<Value>: DynamicProperty {
151+
147152
@usableFromInline
148153
@frozen
149-
enum Content {
154+
internal enum Content: @unchecked Sendable {
155+
156+
/// A key path describing how to dereference the view's current
157+
/// environment to produce the linked value.
150158
case keyPath(KeyPath<EnvironmentValues, Value>)
159+
160+
/// The view's current value of the environment property.
151161
case value(Value)
152162
}
153163

154164
@usableFromInline
155-
var content: Content
165+
internal var content: Content
156166

157167
/// Creates an environment property to read the specified key path.
158168
///
@@ -194,7 +204,6 @@ public struct Environment<Value>: DynamicProperty {
194204
/// }
195205
/// }
196206
///
197-
// Audited for RELEASE_2023
198207
@inlinable
199208
public var wrappedValue: Value {
200209
switch content {
@@ -231,7 +240,7 @@ public struct Environment<Value>: DynamicProperty {
231240
}
232241

233242
@usableFromInline
234-
func error() -> Never {
243+
internal func error() -> Never {
235244
preconditionFailure("Reading Environment<\(Value.self)> outside View.body")
236245
}
237246

@@ -241,52 +250,96 @@ public struct Environment<Value>: DynamicProperty {
241250
fieldOffset: Int,
242251
inputs: inout _GraphInputs
243252
) {
244-
buffer.append(
245-
EnvironmentBox<Value>(
246-
environment: inputs.environment
247-
),
248-
fieldOffset: fieldOffset
249-
)
253+
if Value.self == EnvironmentValues.self {
254+
buffer.append(
255+
FullEnvironmentBox(environment: inputs.environment),
256+
fieldOffset: fieldOffset
257+
)
258+
} else {
259+
buffer.append(
260+
EnvironmentBox<Value>(environment: inputs.environment),
261+
fieldOffset: fieldOffset
262+
)
263+
}
250264
}
251265
}
252266

253267
@available(OpenSwiftUI_v1_0, *)
254268
extension Environment: Sendable where Value: Sendable {}
255269

256-
private struct EnvironmentBox<Value>: DynamicPropertyBox {
270+
// MARK: - FullEnvironmentBox
271+
272+
private struct FullEnvironmentBox: DynamicPropertyBox {
257273
@Attribute var environment: EnvironmentValues
258-
var keyPath: KeyPath<EnvironmentValues, Value>?
259-
var value: Value?
260-
261-
init(environment: Attribute<EnvironmentValues>) {
262-
_environment = environment
263-
keyPath = nil
264-
value = nil
265-
}
266-
267-
func destroy() {}
268-
func reset() {}
269-
mutating func update(property: inout Environment<Value>, phase _: _GraphInputs.Phase) -> Bool {
274+
var keyPath: KeyPath<EnvironmentValues, EnvironmentValues>?
275+
var value: EnvironmentValues?
276+
var tracker: PropertyList.Tracker = .init()
277+
278+
typealias Property = Environment<EnvironmentValues>
279+
280+
mutating func update(property: inout Property, phase _: ViewPhase) -> Bool {
270281
guard case let .keyPath(propertyKeyPath) = property.content else {
271282
return false
272283
}
273-
let (environment, environmentChanged) = _environment.changedValue()
284+
let (environment, environmentChanged) = $environment.changedValue()
274285
let keyPathChanged = (propertyKeyPath != keyPath)
275-
if keyPathChanged { keyPath = propertyKeyPath }
286+
if keyPathChanged {
287+
keyPath = propertyKeyPath
288+
}
276289
let valueChanged: Bool
277290
if keyPathChanged || environmentChanged {
278291
let newValue = environment[keyPath: propertyKeyPath]
292+
if let value, !tracker.hasDifferentUsedValues(environment.plist) {
293+
valueChanged = false
294+
} else {
295+
tracker.reset()
296+
tracker.initializeValues(from: newValue.plist)
297+
value = EnvironmentValues(newValue.plist, tracker: tracker)
298+
valueChanged = true
299+
}
300+
} else {
301+
valueChanged = false
302+
}
303+
property.content = .value(value!)
304+
return valueChanged
305+
}
306+
}
307+
308+
// MARK: - EnvironmentBox
309+
310+
private struct EnvironmentBox<Value>: DynamicPropertyBox {
311+
@Attribute var environment: EnvironmentValues
312+
var keyPath: KeyPath<EnvironmentValues, Value>?
313+
var value: Value?
314+
var hadObservation: Bool = false
315+
316+
typealias Property = Environment<Value>
317+
318+
mutating func update(property: inout Property, phase _: ViewPhase) -> Bool {
319+
guard case let .keyPath(propertyKeyPath) = property.content else {
320+
return false
321+
}
322+
let (environment, environmentChanged) = $environment.changedValue()
323+
let keyPathChanged = (propertyKeyPath != keyPath)
324+
var valueChanged = environmentChanged
325+
if keyPathChanged {
326+
keyPath = propertyKeyPath
327+
valueChanged = true
328+
}
329+
if keyPathChanged || environmentChanged || hadObservation {
330+
let (newValue, accessList) = _withObservation {
331+
environment[keyPath: propertyKeyPath]
332+
}
333+
hadObservation = accessList != nil
279334
if let value, compareValues(value, newValue) {
280335
valueChanged = false
281336
} else {
282337
value = newValue
283-
valueChanged = true
284338
}
285339
} else {
286340
valueChanged = false
287341
}
288-
let value: Value = self.value!
289-
property.content = .value(value)
342+
property.content = .value(value!)
290343
return valueChanged
291344
}
292345
}

0 commit comments

Comments
 (0)