Skip to content

Commit 2394524

Browse files
authored
[Observation] Tracking adjustments for stability and correctness (#67233) (#67372)
* [Observation] Correct tracking such that recursive but disperate changes can be tracked without crashing * [Observation] Adjust the SPI interface for tracking to support deferred cancellation of events and handle both willSet and didSet events
1 parent 3ec0149 commit 2394524

File tree

3 files changed

+460
-71
lines changed

3 files changed

+460
-71
lines changed

stdlib/public/Observation/Sources/Observation/ObservationRegistrar.swift

Lines changed: 265 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,161 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
/// Provides storage for tracking and access to data changes.
13+
///
14+
/// You don't need to create an instance of `ObservationRegistrar` when using
15+
/// the ``Observation/Observable-swift.macro`` macro to indicate observability
16+
/// of a type.
1217
@available(SwiftStdlib 5.9, *)
1318
public struct ObservationRegistrar: Sendable {
14-
struct State: @unchecked Sendable {
15-
struct Observation {
16-
var properties: Set<AnyKeyPath>
17-
var observer: @Sendable () -> Void
19+
internal class ValueObservationStorage {
20+
func emit<Element>(_ element: Element) -> Bool { return false }
21+
func cancel() { }
22+
}
23+
24+
private struct ValuesObserver {
25+
private let storage: ValueObservationStorage
26+
27+
internal init(storage: ValueObservationStorage) {
28+
self.storage = storage
29+
}
30+
31+
internal func emit<Element>(_ element: Element) -> Bool {
32+
storage.emit(element)
33+
}
34+
35+
internal func cancel() {
36+
storage.cancel()
37+
}
38+
}
39+
40+
private struct State: @unchecked Sendable {
41+
private enum ObservationKind {
42+
case willSetTracking(@Sendable () -> Void)
43+
case didSetTracking(@Sendable () -> Void)
44+
case computed(@Sendable (Any) -> Void)
45+
case values(ValuesObserver)
46+
}
47+
48+
private struct Observation {
49+
private var kind: ObservationKind
50+
internal var properties: Set<AnyKeyPath>
51+
52+
internal init(kind: ObservationKind, properties: Set<AnyKeyPath>) {
53+
self.kind = kind
54+
self.properties = properties
55+
}
56+
57+
var willSetTracker: (@Sendable () -> Void)? {
58+
switch kind {
59+
case .willSetTracking(let tracker):
60+
return tracker
61+
default:
62+
return nil
63+
}
64+
}
65+
66+
var didSetTracker: (@Sendable () -> Void)? {
67+
switch kind {
68+
case .didSetTracking(let tracker):
69+
return tracker
70+
default:
71+
return nil
72+
}
73+
}
74+
75+
var observer: (@Sendable (Any) -> Void)? {
76+
switch kind {
77+
case .computed(let observer):
78+
return observer
79+
default:
80+
return nil
81+
}
82+
}
83+
84+
var isValueObserver: Bool {
85+
switch kind {
86+
case .values:
87+
return true
88+
default:
89+
return false
90+
}
91+
}
92+
93+
func emit<Element>(_ value: Element) -> Bool {
94+
switch kind {
95+
case .values(let observer):
96+
return observer.emit(value)
97+
default:
98+
return false
99+
}
100+
}
101+
102+
func cancel() {
103+
switch kind {
104+
case .values(let observer):
105+
observer.cancel()
106+
default:
107+
break
108+
}
109+
}
18110
}
19111

20-
var id = 0
21-
var observations = [Int : Observation]()
22-
var lookups = [AnyKeyPath : Set<Int>]()
112+
private var id = 0
113+
private var observations = [Int : Observation]()
114+
private var lookups = [AnyKeyPath : Set<Int>]()
23115

24-
mutating func generateId() -> Int {
116+
internal mutating func generateId() -> Int {
25117
defer { id &+= 1 }
26118
return id
27119
}
28120

29-
mutating func registerTracking(for properties: Set<AnyKeyPath>, observer: @Sendable @escaping () -> Void) -> Int {
121+
internal mutating func registerTracking(for properties: Set<AnyKeyPath>, willSet observer: @Sendable @escaping () -> Void) -> Int {
122+
let id = generateId()
123+
observations[id] = Observation(kind: .willSetTracking(observer), properties: properties)
124+
for keyPath in properties {
125+
lookups[keyPath, default: []].insert(id)
126+
}
127+
return id
128+
}
129+
130+
internal mutating func registerTracking(for properties: Set<AnyKeyPath>, didSet observer: @Sendable @escaping () -> Void) -> Int {
131+
let id = generateId()
132+
observations[id] = Observation(kind: .didSetTracking(observer), properties: properties)
133+
for keyPath in properties {
134+
lookups[keyPath, default: []].insert(id)
135+
}
136+
return id
137+
}
138+
139+
internal mutating func registerComputedValues(for properties: Set<AnyKeyPath>, observer: @Sendable @escaping (Any) -> Void) -> Int {
140+
let id = generateId()
141+
observations[id] = Observation(kind: .computed(observer), properties: properties)
142+
for keyPath in properties {
143+
lookups[keyPath, default: []].insert(id)
144+
}
145+
return id
146+
}
147+
148+
internal mutating func registerValues(for properties: Set<AnyKeyPath>, storage: ValueObservationStorage) -> Int {
30149
let id = generateId()
31-
observations[id] = Observation(properties: properties, observer: observer)
150+
observations[id] = Observation(kind: .values(ValuesObserver(storage: storage)), properties: properties)
32151
for keyPath in properties {
33152
lookups[keyPath, default: []].insert(id)
34153
}
35154
return id
36155
}
37156

38-
mutating func cancel(_ id: Int) {
39-
if let tracking = observations.removeValue(forKey: id) {
40-
for keyPath in tracking.properties {
157+
internal func valueObservers(for keyPath: AnyKeyPath) -> Set<Int> {
158+
guard let ids = lookups[keyPath] else {
159+
return []
160+
}
161+
return ids.filter { observations[$0]?.isValueObserver == true }
162+
}
163+
164+
internal mutating func cancel(_ id: Int) {
165+
if let observation = observations.removeValue(forKey: id) {
166+
for keyPath in observation.properties {
41167
if var ids = lookups[keyPath] {
42168
ids.remove(id)
43169
if ids.count == 0 {
@@ -47,52 +173,144 @@ public struct ObservationRegistrar: Sendable {
47173
}
48174
}
49175
}
176+
observation.cancel()
177+
}
178+
}
179+
180+
internal mutating func cancelAll() {
181+
for observation in observations.values {
182+
observation.cancel()
183+
}
184+
observations.removeAll()
185+
lookups.removeAll()
186+
}
187+
188+
internal mutating func willSet(keyPath: AnyKeyPath) -> [@Sendable () -> Void] {
189+
var trackers = [@Sendable () -> Void]()
190+
if let ids = lookups[keyPath] {
191+
for id in ids {
192+
if let tracker = observations[id]?.willSetTracker {
193+
trackers.append(tracker)
194+
}
195+
}
50196
}
197+
return trackers
51198
}
52199

53-
mutating func willSet(keyPath: AnyKeyPath) -> [@Sendable () -> Void] {
54-
var observers = [@Sendable () -> Void]()
200+
internal mutating func didSet<Subject: Observable, Member>(keyPath: KeyPath<Subject, Member>) -> ([@Sendable (Any) -> Void], [@Sendable () -> Void]) {
201+
var observers = [@Sendable (Any) -> Void]()
202+
var trackers = [@Sendable () -> Void]()
55203
if let ids = lookups[keyPath] {
56204
for id in ids {
57-
if let observation = observations[id] {
58-
observers.append(observation.observer)
205+
if let observer = observations[id]?.observer {
206+
observers.append(observer)
59207
cancel(id)
60208
}
209+
if let tracker = observations[id]?.didSetTracker {
210+
trackers.append(tracker)
211+
}
212+
}
213+
}
214+
return (observers, trackers)
215+
}
216+
217+
internal mutating func emit<Element>(_ value: Element, ids: Set<Int>) {
218+
for id in ids {
219+
if observations[id]?.emit(value) == true {
220+
cancel(id)
61221
}
62222
}
63-
return observers
64223
}
65224
}
66225

67-
struct Context: Sendable {
68-
let state = _ManagedCriticalState(State())
226+
internal struct Context: Sendable {
227+
private let state = _ManagedCriticalState(State())
228+
229+
internal var id: ObjectIdentifier { state.id }
69230

70-
var id: ObjectIdentifier { state.id }
231+
internal func registerTracking(for properties: Set<AnyKeyPath>, willSet observer: @Sendable @escaping () -> Void) -> Int {
232+
state.withCriticalRegion { $0.registerTracking(for: properties, willSet: observer) }
233+
}
234+
235+
internal func registerTracking(for properties: Set<AnyKeyPath>, didSet observer: @Sendable @escaping () -> Void) -> Int {
236+
state.withCriticalRegion { $0.registerTracking(for: properties, didSet: observer) }
237+
}
71238

72-
func registerTracking(for properties: Set<AnyKeyPath>, observer: @Sendable @escaping () -> Void) -> Int {
73-
state.withCriticalRegion { $0.registerTracking(for: properties, observer: observer) }
239+
internal func registerComputedValues(for properties: Set<AnyKeyPath>, observer: @Sendable @escaping (Any) -> Void) -> Int {
240+
state.withCriticalRegion { $0.registerComputedValues(for: properties, observer: observer) }
74241
}
75242

76-
func cancel(_ id: Int) {
243+
internal func registerValues(for properties: Set<AnyKeyPath>, storage: ValueObservationStorage) -> Int {
244+
state.withCriticalRegion { $0.registerValues(for: properties, storage: storage) }
245+
}
246+
247+
internal func cancel(_ id: Int) {
77248
state.withCriticalRegion { $0.cancel(id) }
78249
}
250+
251+
internal func cancelAll() {
252+
state.withCriticalRegion { $0.cancelAll() }
253+
}
79254

80-
func willSet<Subject, Member>(
255+
internal func willSet<Subject: Observable, Member>(
81256
_ subject: Subject,
82257
keyPath: KeyPath<Subject, Member>
83258
) {
84-
let actions = state.withCriticalRegion { $0.willSet(keyPath: keyPath) }
85-
for action in actions {
259+
let tracking = state.withCriticalRegion { $0.willSet(keyPath: keyPath) }
260+
for action in tracking {
261+
action()
262+
}
263+
}
264+
265+
internal func didSet<Subject: Observable, Member>(
266+
_ subject: Subject,
267+
keyPath: KeyPath<Subject, Member>
268+
) {
269+
let (ids, (actions, tracking)) = state.withCriticalRegion { ($0.valueObservers(for: keyPath), $0.didSet(keyPath: keyPath)) }
270+
if !ids.isEmpty {
271+
let value = subject[keyPath: keyPath]
272+
state.withCriticalRegion { $0.emit(value, ids: ids) }
273+
}
274+
for action in tracking {
86275
action()
87276
}
277+
for action in actions {
278+
action(subject)
279+
}
280+
}
281+
}
282+
283+
private final class Extent: @unchecked Sendable {
284+
let context = Context()
285+
286+
init() {
287+
}
288+
289+
deinit {
290+
context.cancelAll()
88291
}
89292
}
90293

91-
let context = Context()
294+
internal var context: Context {
295+
return extent.context
296+
}
92297

298+
private var extent = Extent()
299+
300+
/// Creates an instance of the observation registrar.
301+
///
302+
/// You don't need to create an instance of
303+
/// ``Observation/ObservationRegistrar`` when using the
304+
/// ``Observation/Observable-swift.macro`` macro to indicate observably
305+
/// of a type.
93306
public init() {
94307
}
95308

309+
/// Registers access to a specific property for observation.
310+
///
311+
/// - Parameters:
312+
/// - subject: An instance of an observable type.
313+
/// - keyPath: The key path of an observed property.
96314
public func access<Subject: Observable, Member>(
97315
_ subject: Subject,
98316
keyPath: KeyPath<Subject, Member>
@@ -106,20 +324,37 @@ public struct ObservationRegistrar: Sendable {
106324
}
107325
}
108326

327+
/// A property observation called before setting the value of the subject.
328+
///
329+
/// - Parameters:
330+
/// - subject: An instance of an observable type.
331+
/// - keyPath: The key path of an observed property.
109332
public func willSet<Subject: Observable, Member>(
110333
_ subject: Subject,
111334
keyPath: KeyPath<Subject, Member>
112335
) {
113336
context.willSet(subject, keyPath: keyPath)
114337
}
115-
338+
339+
/// A property observation called after setting the value of the subject.
340+
///
341+
/// - Parameters:
342+
/// - subject: An instance of an observable type.
343+
/// - keyPath: The key path of an observed property.
116344
public func didSet<Subject: Observable, Member>(
117345
_ subject: Subject,
118346
keyPath: KeyPath<Subject, Member>
119347
) {
120-
348+
context.didSet(subject, keyPath: keyPath)
121349
}
122350

351+
/// Identifies mutations to the transactions registered for observers.
352+
///
353+
/// This method calls ``willset(_:keypath:)`` before the mutation. Then it
354+
/// calls ``didset(_:keypath:)`` after the mutation.
355+
/// - Parameters:
356+
/// - of: An instance of an observable type.
357+
/// - keyPath: The key path of an observed property.
123358
public func withMutation<Subject: Observable, Member, T>(
124359
of subject: Subject,
125360
keyPath: KeyPath<Subject, Member>,

0 commit comments

Comments
 (0)