From a6d463ab2b4eed9a0e7ffc8b99c6337ebf44635d Mon Sep 17 00:00:00 2001 From: tera-ny Date: Thu, 31 Jul 2025 15:13:04 +0900 Subject: [PATCH 1/3] Remove global variables --- .../UserDefaults+Observation.swift | 3 +-- .../UserDefaultsObservationTests.swift | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftUserDefaults/UserDefaults+Observation.swift b/Sources/SwiftUserDefaults/UserDefaults+Observation.swift index 139f75d..df6061e 100644 --- a/Sources/SwiftUserDefaults/UserDefaults+Observation.swift +++ b/Sources/SwiftUserDefaults/UserDefaults+Observation.swift @@ -22,8 +22,6 @@ import Foundation -private var userDefaultsObserverContext = 0 - public extension UserDefaults { /// Observes changes to the object associated with the specified key. /// @@ -59,6 +57,7 @@ public extension UserDefaults { let userDefaults: UserDefaults let keyPath: String let handler: (Change) -> Void + private var userDefaultsObserverContext = 0 private(set) var isRegistered: Bool = false diff --git a/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift b/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift index dedd3f2..debf936 100644 --- a/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift +++ b/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift @@ -54,6 +54,25 @@ final class UserDefaultsObservationTests: XCTestCase { XCTAssertEqual(changes.map(\.value) as NSArray, [nil, "Test", nil, "Default", 1] as NSArray) XCTAssertEqual(changes.map(\.label), [.initial, .update, .update, .update, .update]) } + + func testInvalidateOnDeinit() { + // Given an observer is registered + var changes: [UserDefaults.Change] = [] + var observer: UserDefaults.Observation? = userDefaults.observeObject(forKey: "TestKey") { change in + changes.append(change) + } + + userDefaults.set("Test", forKey: "TestKey") + + // When the observer is deallocated + observer = nil + + // Then no further changes should be recorded + userDefaults.set("NewTest", forKey: "TestKey") + + XCTAssertEqual(changes.map(\.value) as NSArray, [nil, "Test"] as NSArray) + XCTAssertEqual(changes.map(\.label), [.initial, .update]) + } } private extension UserDefaults.Change { From 2a5cebca50bba53adac8668c3616cf8016aee2ed Mon Sep 17 00:00:00 2001 From: tera-ny Date: Thu, 31 Jul 2025 15:13:34 +0900 Subject: [PATCH 2/3] Add sendable --- Sources/SwiftUserDefaults/UserDefaults+Key.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftUserDefaults/UserDefaults+Key.swift b/Sources/SwiftUserDefaults/UserDefaults+Key.swift index 02796a4..3515772 100644 --- a/Sources/SwiftUserDefaults/UserDefaults+Key.swift +++ b/Sources/SwiftUserDefaults/UserDefaults+Key.swift @@ -65,7 +65,7 @@ public extension UserDefaults { /// /// let rawValue = UserDefaults.standard.string(forKey: UserDefaults.Key.userState.rawValue) /// ``` - struct Key: RawRepresentable, Hashable { + struct Key: RawRepresentable, Hashable, Sendable { /// The underlying string value that is used for assigning a value against within the user defaults. public let rawValue: String From f4afe9d004e97dfc0677f26d6ac7b64de2ed5f15 Mon Sep 17 00:00:00 2001 From: tera-ny Date: Thu, 31 Jul 2025 15:27:00 +0900 Subject: [PATCH 3/3] Enable swift6 mode --- Package.swift | 2 +- Tests/SwiftUserDefaultsTests/UserDefaultTests.swift | 10 +++++----- .../UserDefaultsObservationTests.swift | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index 00ffeec..e5a5ce5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Tests/SwiftUserDefaultsTests/UserDefaultTests.swift b/Tests/SwiftUserDefaultsTests/UserDefaultTests.swift index 79513d9..6fc95d5 100644 --- a/Tests/SwiftUserDefaultsTests/UserDefaultTests.swift +++ b/Tests/SwiftUserDefaultsTests/UserDefaultTests.swift @@ -90,7 +90,6 @@ final class UserDefaultTests: XCTestCase { var changes: [UserDefaults.Change] = [] let observer = wrapper.addObserver { changes.append($0) } - addTeardownBlock(observer.invalidate) wrapper.wrappedValue = "One" wrapper.reset() @@ -98,6 +97,7 @@ final class UserDefaultTests: XCTestCase { userDefaults.x.set("Three", forKey: .init("StringKey")) XCTAssertEqual(changes, [.initial(""), .update("One"), .update(""), .update("Two"), .update("Three")]) + observer.invalidate() } func testCodableWithDefault() { @@ -107,7 +107,6 @@ final class UserDefaultTests: XCTestCase { // Observe changes var changes: [Subject] = [] let token = wrapper.addObserver(handler: { changes.append($0.value) }) - addTeardownBlock(token.invalidate) // Uses default XCTAssertNil(userDefaults.object(forKey: key.rawValue)) @@ -133,6 +132,7 @@ final class UserDefaultTests: XCTestCase { "default", "default" ]) + token.invalidate() } func testCodable() { @@ -142,7 +142,6 @@ final class UserDefaultTests: XCTestCase { // Observe changes var changes: [Subject?] = [] let token = wrapper.addObserver(handler: { changes.append($0.value) }) - addTeardownBlock(token.invalidate) // nil when unset XCTAssertNil(userDefaults.object(forKey: key.rawValue)) @@ -175,6 +174,7 @@ final class UserDefaultTests: XCTestCase { "value", nil ]) + token.invalidate() } func testRawRepresentableWithDefault() { @@ -184,7 +184,6 @@ final class UserDefaultTests: XCTestCase { // Observe changes var changes: [RawSubject] = [] let token = wrapper.addObserver(handler: { changes.append($0.value) }) - addTeardownBlock(token.invalidate) // Uses default XCTAssertNil(userDefaults.object(forKey: key.rawValue)) @@ -210,6 +209,7 @@ final class UserDefaultTests: XCTestCase { .foo, .foo ]) + token.invalidate() } func testRawRepresentable() { @@ -219,7 +219,6 @@ final class UserDefaultTests: XCTestCase { // Observe changes var changes: [RawSubject?] = [] let token = wrapper.addObserver(handler: { changes.append($0.value) }) - addTeardownBlock(token.invalidate) // Uses default XCTAssertNil(userDefaults.object(forKey: key.rawValue)) @@ -251,5 +250,6 @@ final class UserDefaultTests: XCTestCase { nil, .baz ]) + token.invalidate() } } diff --git a/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift b/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift index debf936..c30610e 100644 --- a/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift +++ b/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift @@ -58,9 +58,11 @@ final class UserDefaultsObservationTests: XCTestCase { func testInvalidateOnDeinit() { // Given an observer is registered var changes: [UserDefaults.Change] = [] + var observer: UserDefaults.Observation? = userDefaults.observeObject(forKey: "TestKey") { change in changes.append(change) } + _ = observer userDefaults.set("Test", forKey: "TestKey")