diff --git a/Sources/Defaults/Observation+Combine.swift b/Sources/Defaults/Observation+Combine.swift index c854e50..2cd1307 100644 --- a/Sources/Defaults/Observation+Combine.swift +++ b/Sources/Defaults/Observation+Combine.swift @@ -11,9 +11,9 @@ extension Defaults { final class DefaultsSubscription: Subscription where SubscriberType.Input == BaseChange { private var subscriber: SubscriberType? private var observation: UserDefaultsKeyObservation? - private let options: NSKeyValueObservingOptions + private let options: ObservationOptions - init(subscriber: SubscriberType, suite: UserDefaults, key: String, options: NSKeyValueObservingOptions) { + init(subscriber: SubscriberType, suite: UserDefaults, key: String, options: ObservationOptions) { self.subscriber = subscriber self.options = options self.observation = UserDefaultsKeyObservation( @@ -52,9 +52,9 @@ extension Defaults { private let suite: UserDefaults private let key: String - private let options: NSKeyValueObservingOptions + private let options: ObservationOptions - init(suite: UserDefaults, key: String, options: NSKeyValueObservingOptions) { + init(suite: UserDefaults, key: String, options: ObservationOptions) { self.suite = suite self.key = key self.options = options @@ -91,8 +91,8 @@ extension Defaults { */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( - _ key: Defaults.Key, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + _ key: Key, + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> { let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options) .map { KeyChange(change: $0, defaultValue: key.defaultValue) } @@ -105,8 +105,8 @@ extension Defaults { */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( - _ key: Defaults.NSSecureCodingKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + _ key: NSSecureCodingKey, + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> { let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options) .map { NSSecureCodingKeyChange(change: $0, defaultValue: key.defaultValue) } @@ -119,8 +119,8 @@ extension Defaults { */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( - _ key: Defaults.NSSecureCodingOptionalKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + _ key: NSSecureCodingOptionalKey, + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> { let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options) .map { NSSecureCodingOptionalKeyChange(change: $0) } @@ -133,8 +133,8 @@ extension Defaults { */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( - keys: Defaults.Key..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + keys:Key..., + options: ObservationOptions = [.initial] ) -> AnyPublisher { let initial = Empty(completeImmediately: false).eraseToAnyPublisher() @@ -156,7 +156,7 @@ extension Defaults { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( keys: Defaults.NSSecureCodingKey..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher { let initial = Empty(completeImmediately: false).eraseToAnyPublisher() @@ -178,7 +178,7 @@ extension Defaults { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( keys: Defaults.NSSecureCodingOptionalKey..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher { let initial = Empty(completeImmediately: false).eraseToAnyPublisher() diff --git a/Sources/Defaults/Observation.swift b/Sources/Defaults/Observation.swift index f01ff5c..b77a483 100644 --- a/Sources/Defaults/Observation.swift +++ b/Sources/Defaults/Observation.swift @@ -27,6 +27,16 @@ public protocol DefaultsObservation: AnyObject { extension Defaults { public typealias Observation = DefaultsObservation + public enum ObservationOption { + /// Whether a notification should be sent to the observer immediately, before the observer registration method even returns. + case initial + + /// Whether separate notifications should be sent to the observer before and after each change, instead of a single notification after the change. + case prior + } + + public typealias ObservationOptions = Set + private static func deserialize(_ value: Any?, to type: Value.Type) -> Value? { guard let value = value, @@ -131,7 +141,7 @@ extension Defaults { } } - final class UserDefaultsKeyObservation: NSObject, Defaults.Observation { + final class UserDefaultsKeyObservation: NSObject, Observation { typealias Callback = (BaseChange) -> Void private weak var object: UserDefaults? @@ -148,8 +158,8 @@ extension Defaults { invalidate() } - func start(options: NSKeyValueObservingOptions) { - object?.addObserver(self, forKeyPath: key, options: options, context: nil) + func start(options: ObservationOptions) { + object?.addObserver(self, forKeyPath: key, options: options.toNSKeyValueObservingOptions, context: nil) } public func invalidate() { @@ -210,10 +220,10 @@ extension Defaults { ``` */ public static func observe( - _ key: Defaults.Key, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + _ key: Key, + options: ObservationOptions = [.initial], handler: @escaping (KeyChange) -> Void - ) -> Defaults.Observation { + ) -> Observation { let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in handler( KeyChange(change: change, defaultValue: key.defaultValue) @@ -228,10 +238,10 @@ extension Defaults { */ @available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) public static func observe( - _ key: Defaults.NSSecureCodingKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + _ key: NSSecureCodingKey, + options: ObservationOptions = [.initial], handler: @escaping (NSSecureCodingKeyChange) -> Void - ) -> Defaults.Observation { + ) -> Observation { let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in handler( NSSecureCodingKeyChange(change: change, defaultValue: key.defaultValue) @@ -246,10 +256,10 @@ extension Defaults { */ @available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) public static func observe( - _ key: Defaults.NSSecureCodingOptionalKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + _ key: NSSecureCodingOptionalKey, + options: ObservationOptions = [.initial], handler: @escaping (NSSecureCodingOptionalKeyChange) -> Void - ) -> Defaults.Observation { + ) -> Observation { let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in handler( NSSecureCodingOptionalKeyChange(change: change) @@ -259,3 +269,17 @@ extension Defaults { return observation } } + +extension Defaults.ObservationOptions { + var toNSKeyValueObservingOptions: NSKeyValueObservingOptions { + var options: NSKeyValueObservingOptions = [.old, .new] + + if contains(.initial) { + options.insert(.initial) + } else if contains(.prior) { + options.insert(.prior) + } + + return options + } +} diff --git a/Tests/DefaultsTests/DefaultsTests.swift b/Tests/DefaultsTests/DefaultsTests.swift index 9794e84..52813f4 100644 --- a/Tests/DefaultsTests/DefaultsTests.swift +++ b/Tests/DefaultsTests/DefaultsTests.swift @@ -169,7 +169,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") let publisher = Defaults - .publisher(key, options: [.old, .new]) + .publisher(key, options: []) .map { ($0.oldValue, $0.newValue) } .collect(2) @@ -195,7 +195,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") let publisher = Defaults - .publisher(key, options: [.old, .new]) + .publisher(key, options: []) .map { ($0.oldValue.value, $0.newValue.value) } .collect(3) @@ -228,7 +228,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") let publisher = Defaults - .publisher(key, options: [.old, .new]) + .publisher(key, options: []) .map { ($0.oldValue, $0.newValue) } .collect(3) @@ -257,7 +257,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") let publisher = Defaults - .publisher(key, options: [.old, .new]) + .publisher(key, options: []) .map { ($0.oldValue?.value, $0.newValue?.value) } .collect(3) @@ -309,7 +309,7 @@ final class DefaultsTests: XCTestCase { let key2 = Defaults.Key("observeKey2", default: true) let expect = expectation(description: "Observation closure being called") - let publisher = Defaults.publisher(keys: key1, key2, options: [.old, .new]).collect(2) + let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2) let cancellable = publisher.sink { _ in expect.fulfill() @@ -329,7 +329,7 @@ final class DefaultsTests: XCTestCase { let key2 = Defaults.NSSecureCodingKey("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue")) let expect = expectation(description: "Observation closure being called") - let publisher = Defaults.publisher(keys: key1, key2, options: [.old, .new]).collect(2) + let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2) let cancellable = publisher.sink { _ in expect.fulfill() @@ -349,7 +349,7 @@ final class DefaultsTests: XCTestCase { let key2 = Defaults.Key("observeOptionalKey2") let expect = expectation(description: "Observation closure being called") - let publisher = Defaults.publisher(keys: key1, key2, options: [.old, .new]).collect(2) + let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2) let cancellable = publisher.sink { _ in expect.fulfill() @@ -368,7 +368,7 @@ final class DefaultsTests: XCTestCase { let key2 = Defaults.NSSecureCodingOptionalKey("observeNSSecureCodingKey2") let expect = expectation(description: "Observation closure being called") - let publisher = Defaults.publisher(keys: key1, key2, options: [.old, .new]).collect(2) + let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2) let cancellable = publisher.sink { _ in expect.fulfill() @@ -387,7 +387,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") let publisher = Defaults - .publisher(key, options: [.initial, .new]) + .publisher(key) .compactMap { $0.newValue } .eraseToAnyPublisher() .collect(2) @@ -407,7 +407,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertFalse(change.oldValue) XCTAssertTrue(change.newValue) observation.invalidate() @@ -425,7 +425,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertEqual(change.oldValue.value, "TestValue") XCTAssertEqual(change.newValue.value, "NewTestValue") observation.invalidate() @@ -442,7 +442,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertNil(change.oldValue) XCTAssertTrue(change.newValue!) observation.invalidate() @@ -460,7 +460,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertNil(change.oldValue) XCTAssertEqual(change.newValue?.value, "NewOptionalValue") observation.invalidate() @@ -479,7 +479,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertEqual(change.oldValue, fixtureURL) XCTAssertEqual(change.newValue, fixtureURL2) observation.invalidate() @@ -496,7 +496,7 @@ final class DefaultsTests: XCTestCase { let expect = expectation(description: "Observation closure being called") var observation: Defaults.Observation! - observation = Defaults.observe(key, options: [.old, .new]) { change in + observation = Defaults.observe(key, options: []) { change in XCTAssertEqual(change.oldValue, .oneHour) XCTAssertEqual(change.newValue, .tenMinutes) observation.invalidate() diff --git a/readme.md b/readme.md index 043c492..66bd423 100644 --- a/readme.md +++ b/readme.md @@ -339,7 +339,7 @@ Reset the given keys back to their default values. ```swift Defaults.observe( _ key: Defaults.Key, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + options: ObservationOptions = [.initial], handler: @escaping (KeyChange) -> Void ) -> Defaults.Observation ``` @@ -347,7 +347,7 @@ Defaults.observe( ```swift Defaults.observe( _ key: Defaults.NSSecureCodingKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + options: ObservationOptions = [.initial], handler: @escaping (NSSecureCodingKeyChange) -> Void ) -> Defaults.Observation ``` @@ -355,7 +355,7 @@ Defaults.observe( ```swift Defaults.observe( _ key: Defaults.NSSecureCodingOptionalKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new], + options: ObservationOptions = [.initial], handler: @escaping (NSSecureCodingOptionalKeyChange) -> Void ) -> Defaults.Observation ``` @@ -371,21 +371,21 @@ By default, it will also trigger an initial event on creation. This can be usefu ```swift Defaults.publisher( _ key: Defaults.Key, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> ``` ```swift Defaults.publisher( _ key: Defaults.NSSecureCodingKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> ``` ```swift Defaults.publisher( _ key: Defaults.NSSecureCodingOptionalKey, - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher, Never> ``` @@ -400,21 +400,21 @@ Available on macOS 10.15+, iOS 13.0+, tvOS 13.0+, and watchOS 6.0+. ```swift Defaults.publisher( keys: Defaults.Key..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher { ``` ```swift Defaults.publisher( keys: Defaults.NSSecureCodingKey..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher { ``` ```swift Defaults.publisher( keys: Defaults.NSSecureCodingOptionalKey..., - options: NSKeyValueObservingOptions = [.initial, .old, .new] + options: ObservationOptions = [.initial] ) -> AnyPublisher { ```