Skip to content

Commit 9d1d917

Browse files
authored
[Observation] ensure event triggers on deinitialization passes as if all properties that are being observed have changed (for weak storage) (#79823)
* [Observation] ensure event triggers on deinitialziation passes as if all properties that are being observed have changed (for weak storage) * Add missing deinitialize method for synthetically triggering willSet * Correct the weak location for tests * Correct the test to actually test the deinitialization willSet trigger instead of testing weak value deinitialization time * Refine the tests for deinit triggers to more tightly trigger deinitialization and weak references * Correct missing trailing closure on deinit replacement * Ensure all potential ids are triggered at the deinitialization edge trigger
1 parent 77a86a5 commit 9d1d917

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,20 @@ public struct ObservationRegistrar: Sendable {
110110
}
111111
}
112112

113-
internal mutating func cancelAll() {
113+
internal mutating func deinitialize() -> [@Sendable () -> Void] {
114+
var trackers = [@Sendable () -> Void]()
115+
for (keyPath, ids) in lookups {
116+
for id in ids {
117+
if let tracker = observations[id]?.willSetTracker {
118+
trackers.append({
119+
tracker(keyPath)
120+
})
121+
}
122+
}
123+
}
114124
observations.removeAll()
115125
lookups.removeAll()
126+
return trackers
116127
}
117128

118129
internal mutating func willSet(keyPath: AnyKeyPath) -> [@Sendable (AnyKeyPath) -> Void] {
@@ -157,8 +168,11 @@ public struct ObservationRegistrar: Sendable {
157168
state.withCriticalRegion { $0.cancel(id) }
158169
}
159170

160-
internal func cancelAll() {
161-
state.withCriticalRegion { $0.cancelAll() }
171+
internal func deinitialize() {
172+
let tracking = state.withCriticalRegion { $0.deinitialize() }
173+
for action in tracking {
174+
action()
175+
}
162176
}
163177

164178
internal func willSet<Subject: Observable, Member>(
@@ -189,7 +203,7 @@ public struct ObservationRegistrar: Sendable {
189203
}
190204

191205
deinit {
192-
context.cancelAll()
206+
context.deinitialize()
193207
}
194208
}
195209

test/stdlib/Observation/Observable.swift

+31
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,21 @@ final class CowTest {
287287
var container = CowContainer()
288288
}
289289

290+
@Observable
291+
final class DeinitTriggeredObserver {
292+
var property: Int = 3
293+
let deinitTrigger: () -> Void
294+
295+
init(_ deinitTrigger: @escaping () -> Void) {
296+
self.deinitTrigger = deinitTrigger
297+
}
298+
299+
deinit {
300+
deinitTrigger()
301+
}
302+
}
303+
304+
290305
@main
291306
struct Validator {
292307
@MainActor
@@ -511,6 +526,22 @@ struct Validator {
511526
expectEqual(subject.container.id, startId)
512527
}
513528

529+
suite.test("weak container observation") {
530+
let changed = CapturedState(state: false)
531+
let deinitialized = CapturedState(state: false)
532+
var test = DeinitTriggeredObserver {
533+
deinitialized.state = true
534+
}
535+
withObservationTracking { [weak test] in
536+
_blackHole(test?.property)
537+
} onChange: {
538+
changed.state = true
539+
}
540+
test = DeinitTriggeredObserver { }
541+
expectEqual(deinitialized.state, true)
542+
expectEqual(changed.state, true)
543+
}
544+
514545
runAllTests()
515546
}
516547
}

0 commit comments

Comments
 (0)