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/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 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/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 dedd3f2..c30610e 100644 --- a/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift +++ b/Tests/SwiftUserDefaultsTests/UserDefaultsObservationTests.swift @@ -54,6 +54,27 @@ 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) + } + _ = observer + + 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 {