11import Foundation
22
3+ // TODO: Document State properly, this is an important type.
4+ // - It supports value types
5+ // - It supports ObservableObject
6+ // - It supports Optional<ObservableObject>
37@propertyWrapper
48public struct State < Value> : DynamicProperty , StateProperty {
59 class Storage {
@@ -9,18 +13,46 @@ public struct State<Value>: DynamicProperty, StateProperty {
913 // `update(with:previousValue:)` method. It's vital that the inner
1014 // box remains the same so that bindings can be stored across view
1115 // updates.
12- var box : Box < Value >
13- var didChange = Publisher ( )
16+ var box : InnerBox
17+
18+ class InnerBox {
19+ var value : Value
20+ var didChange = Publisher ( )
21+ var downstreamObservation : Cancellable ?
22+
23+ init ( value: Value ) {
24+ self . value = value
25+ }
26+
27+ /// Call this to publish an observation to all observers after
28+ /// setting a new value. This isn't in a didSet property accessor
29+ /// because we want more granular control over when it does and
30+ /// doesn't trigger.
31+ func postSet( ) {
32+ // If the wrapped value is an Optional<some ObservableObject>
33+ // then we need to observe/unobserve whenever the optional
34+ // toggles between `.some` and `.none`.
35+ if let value = value as? OptionalObservableObject {
36+ if let innerDidChange = value. didChange, downstreamObservation == nil {
37+ downstreamObservation = didChange. link ( toUpstream: innerDidChange)
38+ } else if value. didChange == nil , let observation = downstreamObservation {
39+ observation. cancel ( )
40+ downstreamObservation = nil
41+ }
42+ }
43+ didChange. send ( )
44+ }
45+ }
1446
1547 init ( _ value: Value ) {
16- self . box = Box ( value: value)
48+ self . box = InnerBox ( value: value)
1749 }
1850 }
1951
2052 var storage : Storage
2153
2254 var didChange : Publisher {
23- storage. didChange
55+ storage. box . didChange
2456 }
2557
2658 public var wrappedValue : Value {
@@ -29,7 +61,7 @@ public struct State<Value>: DynamicProperty, StateProperty {
2961 }
3062 nonmutating set {
3163 storage. box. value = newValue
32- didChange . send ( )
64+ storage . box . postSet ( )
3365 }
3466 }
3567
@@ -43,23 +75,32 @@ public struct State<Value>: DynamicProperty, StateProperty {
4375 } ,
4476 set: { newValue in
4577 box. value = newValue
46- didChange . send ( )
78+ box . postSet ( )
4779 }
4880 )
4981 }
5082
5183 public init ( wrappedValue initialValue: Value ) {
5284 storage = Storage ( initialValue)
5385
54- if let initialValue = initialValue as? ObservableObject {
55- _ = didChange. link ( toUpstream: initialValue. didChange)
86+ // Before casting the value we check the type, because casting an optional
87+ // to protocol Optional doesn't conform to can still succeed when the value
88+ // is `.some` and the wrapped type conforms to the protocol.
89+ if Value . self as? ObservableObject . Type != nil ,
90+ let initialValue = initialValue as? ObservableObject {
91+ storage. box. downstreamObservation = didChange. link ( toUpstream: initialValue. didChange)
92+ } else if let initialValue = initialValue as? OptionalObservableObject ,
93+ let innerDidChange = initialValue. didChange
94+ {
95+ // If we have an Optional<some ObservableObject>.some, then observe its
96+ // inner value's publisher.
97+ storage. box. downstreamObservation = didChange. link ( toUpstream: innerDidChange)
5698 }
5799 }
58100
59101 public func update( with environment: EnvironmentValues , previousValue: State < Value > ? ) {
60102 if let previousValue {
61103 storage. box = previousValue. storage. box
62- storage. didChange = previousValue. storage. didChange
63104 }
64105 }
65106
0 commit comments