Skip to content

Commit f5e6dd7

Browse files
committed
Fix observer deregistration on deinit
1 parent d48a01d commit f5e6dd7

2 files changed

Lines changed: 131 additions & 23 deletions

File tree

Sources/PersistentKeyValueKit/Property Wrapper/PersistentKeyUIObservableObject.swift

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public final class PersistentKeyUIObservableObject<Key>: ObservableObject where
2828

2929
/// The store that the key is being observed in.
3030
private var _store: (any PersistentKeyValueStore)?
31+
32+
/// The registration lifetime for the current store.
33+
private var observation: StoreObservation<Key>?
3134

3235
/// The object used for key-value and `NotificationCenter` observation.
3336
private var observer: Observer<Key>!
@@ -45,8 +48,6 @@ public final class PersistentKeyUIObservableObject<Key>: ObservableObject where
4548

4649
observer = Observer(keyID: key.id) { [weak self] in
4750
self?.objectWillChange.send()
48-
} deregister: { @MainActor [weak self] in
49-
self?.deregisterObserver(on: store)
5051
}
5152

5253
registerObserver(on: store)
@@ -60,14 +61,10 @@ public final class PersistentKeyUIObservableObject<Key>: ObservableObject where
6061
_store
6162
}
6263
set {
63-
deregisterObserver(on: _store)
64+
observation = nil
6465

6566
_store = newValue
6667

67-
observer.deregister = { @MainActor [weak self] in
68-
self?.deregisterObserver(on: newValue)
69-
}
70-
7168
registerObserver(on: newValue)
7269
}
7370
}
@@ -94,18 +91,50 @@ public final class PersistentKeyUIObservableObject<Key>: ObservableObject where
9491
assert(store != nil, "Store should always be set on initialization or immediately after.")
9592
}
9693

97-
private func deregisterObserver(on store: (any PersistentKeyValueStore)?) {
98-
store?.deregister(observer, for: key, context: nil)
99-
}
100-
10194
private func registerObserver(on store: (any PersistentKeyValueStore)?) {
102-
store?.register(
95+
guard let store else {
96+
return
97+
}
98+
99+
observation = StoreObservation(
100+
store: store,
101+
key: key,
102+
observer: observer
103+
)
104+
}
105+
}
106+
107+
// MARK: - StoreObservation Definition
108+
109+
private final class StoreObservation<Key> where Key: PersistentKeyProtocol {
110+
private let key: Key
111+
private let observer: Observer<Key>
112+
private let store: any PersistentKeyValueStore
113+
114+
// MARK: Internal Initialization
115+
116+
internal init(
117+
store: any PersistentKeyValueStore,
118+
key: Key,
119+
observer: Observer<Key>
120+
) {
121+
self.key = key
122+
self.observer = observer
123+
self.store = store
124+
125+
store.register(
103126
observer: observer,
104127
for: key,
105128
with: nil,
106129
and: #selector(Observer<Key>.didReceive(_:))
107130
)
108131
}
132+
133+
// MARK: Deinitialization
134+
135+
deinit {
136+
store.deregister(observer, for: key, context: nil)
137+
}
109138
}
110139

111140
// MARK: - Observer Definition
@@ -114,24 +143,14 @@ private final class Observer<Key>: NSObject, ObservableObject where Key: Persist
114143
internal let keyID: String
115144
internal let objectWillChange: @MainActor () -> Void
116145

117-
internal var deregister: @MainActor () -> Void
118-
119146
// MARK: Internal Initialization
120147

121148
internal init(
122149
keyID: String,
123-
objectWillChange: @escaping @MainActor () -> Void,
124-
deregister: @escaping @MainActor () -> Void
150+
objectWillChange: @escaping @MainActor () -> Void
125151
) {
126152
self.keyID = keyID
127153
self.objectWillChange = objectWillChange
128-
self.deregister = deregister
129-
}
130-
131-
// MARK: Deinitialization
132-
133-
deinit {
134-
performOnMainIfNecessary(deregister)
135154
}
136155

137156
// MARK: NSObject Implementation

Tests/PersistentKeyValueKitTests/Tests/Persistent Key/PersistentKeyObserverTests.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,93 @@ extension PersistentKeyObserverTests {
136136

137137
XCTAssertNil(weakObserver, "Observer should be deallocated")
138138
}
139+
140+
@MainActor
141+
func test_deinitialization_deregistersFromStore() {
142+
let store = ObservablePersistentKeyValueStore()
143+
let key = PersistentKey("key:\(#function)", defaultValue: "defaultValue")
144+
var observer: PersistentKeyUIObservableObject? = PersistentKeyUIObservableObject(store: store, key: key)
145+
146+
XCTAssertEqual(store.registrationCount, 1)
147+
148+
weak let weakObserver = observer
149+
observer = nil
150+
151+
XCTAssertNil(weakObserver)
152+
XCTAssertEqual(store.deregistrationCount, 1)
153+
}
154+
155+
@MainActor
156+
func test_deinitialization_deregistersCurrentStoreAfterStoreChange() {
157+
let oldStore = ObservablePersistentKeyValueStore()
158+
let newStore = ObservablePersistentKeyValueStore()
159+
let key = PersistentKey("key:\(#function)", defaultValue: "defaultValue")
160+
var observer: PersistentKeyUIObservableObject? = PersistentKeyUIObservableObject(store: oldStore, key: key)
161+
162+
XCTAssertEqual(oldStore.registrationCount, 1)
163+
XCTAssertEqual(newStore.registrationCount, 0)
164+
165+
observer?.store = newStore
166+
167+
XCTAssertEqual(oldStore.deregistrationCount, 1)
168+
XCTAssertEqual(newStore.registrationCount, 1)
169+
170+
observer = nil
171+
172+
XCTAssertEqual(oldStore.deregistrationCount, 1)
173+
XCTAssertEqual(newStore.deregistrationCount, 1)
174+
}
175+
176+
@MainActor
177+
func test_deinitialization_deregistersStoreSetAfterNilInitialization() {
178+
let store = ObservablePersistentKeyValueStore()
179+
let key = PersistentKey("key:\(#function)", defaultValue: "defaultValue")
180+
var observer: PersistentKeyUIObservableObject? = PersistentKeyUIObservableObject(store: nil, key: key)
181+
182+
XCTAssertEqual(store.registrationCount, 0)
183+
184+
observer?.store = store
185+
186+
XCTAssertEqual(store.registrationCount, 1)
187+
XCTAssertEqual(store.deregistrationCount, 0)
188+
189+
observer = nil
190+
191+
XCTAssertEqual(store.deregistrationCount, 1)
192+
}
193+
194+
@MainActor
195+
func test_deinitialization_doesNotDeregisterOldStoreAgainAfterStoreChangesToNil() {
196+
let store = ObservablePersistentKeyValueStore()
197+
let key = PersistentKey("key:\(#function)", defaultValue: "defaultValue")
198+
var observer: PersistentKeyUIObservableObject? = PersistentKeyUIObservableObject(store: store, key: key)
199+
200+
XCTAssertEqual(store.registrationCount, 1)
201+
202+
observer?.store = nil
203+
204+
XCTAssertEqual(store.deregistrationCount, 1)
205+
206+
observer = nil
207+
208+
XCTAssertEqual(store.deregistrationCount, 1)
209+
}
210+
211+
@MainActor
212+
func test_deinitialization_deregistersCurrentRegistrationAfterStoreChangesToSameStore() {
213+
let store = ObservablePersistentKeyValueStore()
214+
let key = PersistentKey("key:\(#function)", defaultValue: "defaultValue")
215+
var observer: PersistentKeyUIObservableObject? = PersistentKeyUIObservableObject(store: store, key: key)
216+
217+
XCTAssertEqual(store.registrationCount, 1)
218+
219+
observer?.store = store
220+
221+
XCTAssertEqual(store.registrationCount, 2)
222+
XCTAssertEqual(store.deregistrationCount, 1)
223+
224+
observer = nil
225+
226+
XCTAssertEqual(store.deregistrationCount, 2)
227+
}
139228
}

0 commit comments

Comments
 (0)