1
1
import Foundation
2
2
3
+ // TODO: Document State properly, this is an important type.
4
+ // - It supports value types
5
+ // - It supports ObservableObject
6
+ // - It supports Optional<ObservableObject>
3
7
@propertyWrapper
4
8
public struct State < Value> : DynamicProperty , StateProperty {
5
9
class Storage {
@@ -9,18 +13,46 @@ public struct State<Value>: DynamicProperty, StateProperty {
9
13
// `update(with:previousValue:)` method. It's vital that the inner
10
14
// box remains the same so that bindings can be stored across view
11
15
// 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
+ }
14
46
15
47
init ( _ value: Value ) {
16
- self . box = Box ( value: value)
48
+ self . box = InnerBox ( value: value)
17
49
}
18
50
}
19
51
20
52
var storage : Storage
21
53
22
54
var didChange : Publisher {
23
- storage. didChange
55
+ storage. box . didChange
24
56
}
25
57
26
58
public var wrappedValue : Value {
@@ -29,7 +61,7 @@ public struct State<Value>: DynamicProperty, StateProperty {
29
61
}
30
62
nonmutating set {
31
63
storage. box. value = newValue
32
- didChange . send ( )
64
+ storage . box . postSet ( )
33
65
}
34
66
}
35
67
@@ -43,23 +75,32 @@ public struct State<Value>: DynamicProperty, StateProperty {
43
75
} ,
44
76
set: { newValue in
45
77
box. value = newValue
46
- didChange . send ( )
78
+ box . postSet ( )
47
79
}
48
80
)
49
81
}
50
82
51
83
public init ( wrappedValue initialValue: Value ) {
52
84
storage = Storage ( initialValue)
53
85
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)
56
98
}
57
99
}
58
100
59
101
public func update( with environment: EnvironmentValues , previousValue: State < Value > ? ) {
60
102
if let previousValue {
61
103
storage. box = previousValue. storage. box
62
- storage. didChange = previousValue. storage. didChange
63
104
}
64
105
}
65
106
0 commit comments