Skip to content

Commit 989f8f2

Browse files
committed
Implement proper deregistration and make work back to iOS 15
1 parent 4cc5b3c commit 989f8f2

File tree

8 files changed

+74
-5
lines changed

8 files changed

+74
-5
lines changed

Sources/PersistentKeyValueKit/Persistent Key-Value Store/Extensions/Foundation/NSUbiquitousKeyValueStore+PersistentKeyValueStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ extension NSUbiquitousKeyValueStore: PersistentKeyValueStore {
4242

4343
// MARK: Observing Keys
4444

45+
@inlinable
46+
public func deregister<Key>(
47+
_ observer: NSObject,
48+
for key: Key,
49+
context: UnsafeMutableRawPointer?
50+
) where Key : PersistentKeyProtocol {
51+
// We use selector-based notification APIs that do not need to perform manual observation deregistration.
52+
}
53+
4554
@inlinable
4655
public func register<Key>(
4756
observer target: NSObject,

Sources/PersistentKeyValueKit/Persistent Key-Value Store/Extensions/Foundation/UserDefaults+PersistentKeyValueStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ extension UserDefaults: PersistentKeyValueStore {
3333

3434
// MARK: Observing Keys
3535

36+
@inlinable
37+
public func deregister<Key>(
38+
_ observer: NSObject,
39+
for key: Key,
40+
context: UnsafeMutableRawPointer?
41+
) where Key : PersistentKeyProtocol {
42+
removeObserver(observer, forKeyPath: key.id, context: context)
43+
}
44+
3645
@inlinable
3746
public func register<Key>(
3847
observer target: NSObject,

Sources/PersistentKeyValueKit/Persistent Key-Value Store/Implementations/InMemoryPersistentKeyValueStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ extension InMemoryPersistentKeyValueStore: PersistentKeyValueStore {
6666

6767
// MARK: Observing Keys
6868

69+
@inlinable
70+
public func deregister<Key>(
71+
_ observer: NSObject,
72+
for key: Key,
73+
context: UnsafeMutableRawPointer?
74+
) where Key : PersistentKeyProtocol {
75+
// NO-OP
76+
}
77+
6978
public func register<Key>(
7079
observer target: NSObject,
7180
for key: Key,

Sources/PersistentKeyValueKit/Persistent Key-Value Store/PersistentKeyValueStore.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,34 @@ public protocol PersistentKeyValueStore {
4040

4141
// MARK: Observing Keys
4242

43+
/// Removes an observer for the given key.
44+
///
45+
/// - Important: This is a `NO-OP`, and not a concern, for `NSUbiquitousKeyValueStore`.
46+
/// - Parameter observer: The object to remove as an observer.
47+
/// - Parameter key: The key to stop observing.
48+
/// - Parameter context: Arbitrary data that more specifically identifies the observer to be removed.
49+
func deregister<Key>(
50+
_ observer: NSObject,
51+
for key: Key,
52+
context: UnsafeMutableRawPointer?
53+
) where Key: PersistentKeyProtocol
54+
4355
/// Registers an observer for the given key.
4456
///
57+
/// For `UserDefaults`, neither the object receiving this message, nor observer, are retained. An object that calls
58+
/// this method must also eventually call either the ``deregister(_:forKeyPath:context:)`` method to unregister the
59+
/// observer when participating in KVO.
60+
///
4561
/// - Important: This is used to orchestrate the SwiftUI property wrapper. This is not intended to provide a general
4662
/// way to observe changes to keys: its ergonomics are specifically designed for implementing
4763
/// ``PersistentKeyObserver``, knowing that the implementation is either `UserDefaults` and
4864
/// `NSUbiquitousKeyValueStore`.
49-
///
5065
/// - Parameter observer: The observer to register.
5166
/// - Parameter key: The key to observe.
52-
/// - Parameter context: The context to use for `UserDefaults` key-value observation. This is a NO-OP for
67+
/// - Parameter context: The context to use for `UserDefaults` key-value observation. This is a `NO-OP` for
5368
/// `NSUbiquitousKeyValueStore`.
5469
/// - Parameter selector: The selector to use for `NSUbiquitousKeyValueStore` notification observation. This is a
55-
/// NO-OP for `UserDefaults`.
70+
/// `NO-OP` for `UserDefaults`.
5671
func register<Key>(
5772
observer: NSObject,
5873
for key: Key,

Sources/PersistentKeyValueKit/Persistent Key/PersistentKeyObserver.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public final class PersistentKeyObserver<Key>: ObservableObject where Key: Persi
5757
and: #selector(ObserverObject.didReceive(_:))
5858
)
5959
}
60+
61+
deinit {
62+
_store?.deregister(observerObject, for: key, context: &context)
63+
}
6064

6165
// MARK: Public Instance Interface
6266

@@ -66,6 +70,8 @@ public final class PersistentKeyObserver<Key>: ObservableObject where Key: Persi
6670
_store
6771
}
6872
set {
73+
_store?.deregister(observerObject, for: key, context: &context)
74+
6975
_store = newValue
7076

7177
newValue?.register(

Tests/PersistentKeyValueKitTests/Tests/Persistent Key/AbstractPersistentKeyTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,17 @@ public class AbstractPersistentKeyTests<Key>: XCTestCase where Key: PersistentKe
129129

130130
XCTAssertEqual(userDefaults.dictionaryRepresentation()[id] as? Key.Value, storedValue)
131131
}
132+
133+
// MARK: Internal Instance Interface
134+
135+
/// Skip the test if the environment is not iOS 16, or later, or equivalent.
136+
///
137+
/// There is an issue with the implementation of tryCast across the Objective-C bridge when testing on
138+
/// iOS 15 from Xcode 16. Additionally, marking test cases as available <= iOS 16 is not working in the
139+
/// same environment.
140+
func skipIfNotiOS16OrLaterOrEquivalent() throws {
141+
guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) else {
142+
throw XCTSkip(">= iOS 16 is required for this test.")
143+
}
144+
}
132145
}

Tests/PersistentKeyValueKitTests/Tests/Persistent Key/Implementations/PersistentDebugKeyTests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ final class PersistentDebugKeyTests: AbstractPersistentKeyTests<PersistentDebugK
3030
// MARK: Initialization Tests
3131

3232
@MainActor
33-
func test_init_otherRepresentation() {
33+
func test_init_otherRepresentation() throws {
34+
try skipIfNotiOS16OrLaterOrEquivalent()
35+
3436
let representation = ReferenceProxyPersistentKeyValueRepresentation<String, String>(
3537
serializing: \.self,
3638
deserializing: \.self
@@ -48,6 +50,8 @@ final class PersistentDebugKeyTests: AbstractPersistentKeyTests<PersistentDebugK
4850

4951
@MainActor
5052
func test_init_otherRepresentation_optional() throws {
53+
try skipIfNotiOS16OrLaterOrEquivalent()
54+
5155
let representation = ReferenceProxyPersistentKeyValueRepresentation<String, String>(
5256
serializing: \.self,
5357
deserializing: \.self

Tests/PersistentKeyValueKitTests/Tests/Persistent Key/Implementations/PersistentKeyTests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ final class PersistentKeyTests: AbstractPersistentKeyTests<PersistentKey<String>
3030
// MARK: Initialization Tests
3131

3232
@MainActor
33-
func test_init_otherRepresentation() {
33+
func test_init_otherRepresentation() throws {
34+
try skipIfNotiOS16OrLaterOrEquivalent()
35+
3436
let representation = ReferenceProxyPersistentKeyValueRepresentation<String, String>(
3537
serializing: \.self,
3638
deserializing: \.self
@@ -48,6 +50,8 @@ final class PersistentKeyTests: AbstractPersistentKeyTests<PersistentKey<String>
4850

4951
@MainActor
5052
func test_init_otherRepresentation_optional() throws {
53+
try skipIfNotiOS16OrLaterOrEquivalent()
54+
5155
let representation = ReferenceProxyPersistentKeyValueRepresentation<String, String>(
5256
serializing: \.self,
5357
deserializing: \.self

0 commit comments

Comments
 (0)