Various improvements
This commit is contained in:
parent
ab8127604c
commit
110f2d2b9e
|
@ -33,14 +33,14 @@
|
|||
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||
E3EB3E33216505920033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
||||
E3EB3E33216505920033B089 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* Utilities.swift */; };
|
||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||
E3EB3E39216507C30033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
||||
E3EB3E3A216507C40033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
||||
E3EB3E3B216507C40033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
||||
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* Utilities.swift */; };
|
||||
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* Utilities.swift */; };
|
||||
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* Utilities.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -84,7 +84,7 @@
|
|||
E339B3B22449ED2000E7A40A /* Reset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Reset.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E339B3B72449F10D00E7A40A /* UserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UserDefaults.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftUI.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3EB3E32216505920033B089 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3EB3E32216505920033B089 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Utilities.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3EB3E34216507AE0033B089 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observation.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -221,7 +221,7 @@
|
|||
E3EB3E34216507AE0033B089 /* Observation.swift */,
|
||||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
||||
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
|
||||
E3EB3E32216505920033B089 /* util.swift */,
|
||||
E3EB3E32216505920033B089 /* Utilities.swift */,
|
||||
);
|
||||
path = Defaults;
|
||||
sourceTree = "<group>";
|
||||
|
@ -512,7 +512,7 @@
|
|||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
||||
E3EB3E33216505920033B089 /* util.swift in Sources */,
|
||||
E3EB3E33216505920033B089 /* Utilities.swift in Sources */,
|
||||
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -531,7 +531,7 @@
|
|||
files = (
|
||||
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
E3EB3E3A216507C40033B089 /* util.swift in Sources */,
|
||||
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */,
|
||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
||||
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
|
@ -545,7 +545,7 @@
|
|||
files = (
|
||||
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
E3EB3E3B216507C40033B089 /* util.swift in Sources */,
|
||||
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */,
|
||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
||||
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
|
@ -559,7 +559,7 @@
|
|||
files = (
|
||||
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
E3EB3E39216507C30033B089 /* util.swift in Sources */,
|
||||
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */,
|
||||
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
|
||||
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// MIT License © Sindre Sorhus
|
||||
import Foundation
|
||||
|
||||
public protocol DefaultsBaseKey: Defaults.Keys {
|
||||
public protocol DefaultsBaseKey: Defaults.AnyKey {
|
||||
var name: String { get }
|
||||
var suite: UserDefaults { get }
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ extension DefaultsBaseKey {
|
|||
|
||||
public enum Defaults {
|
||||
public typealias BaseKey = DefaultsBaseKey
|
||||
|
||||
public typealias AnyKey = Keys
|
||||
|
||||
public class Keys: BaseKey {
|
||||
public typealias Key = Defaults.Key
|
||||
|
||||
|
@ -34,7 +35,7 @@ public enum Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
public final class Key<Value: Codable>: Keys {
|
||||
public final class Key<Value: Codable>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
|
||||
/// Create a defaults key.
|
||||
|
@ -58,7 +59,7 @@ public enum 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 final class NSSecureCodingKey<Value: NSSecureCoding>: Keys {
|
||||
public final class NSSecureCodingKey<Value: NSSecureCoding>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
|
||||
/// Create a defaults key.
|
||||
|
@ -82,7 +83,7 @@ public enum 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 final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: Keys {
|
||||
public final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: AnyKey {
|
||||
/// Create an optional defaults key.
|
||||
public init(_ key: String, suite: UserDefaults = .standard) {
|
||||
super.init(name: key, suite: suite)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#if canImport(Combine)
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
|
@ -133,7 +132,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: Keys...,
|
||||
keys: AnyKey...,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<Void, Never> {
|
||||
let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
|
||||
|
@ -150,5 +149,4 @@ extension Defaults {
|
|||
return combinedPublisher
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -140,39 +140,39 @@ extension Defaults {
|
|||
self.newValue = deserialize(change.newValue, to: Value.self)
|
||||
}
|
||||
}
|
||||
|
||||
private static var preventPropagationThreadDictKey: String {
|
||||
|
||||
private static var preventPropagationThreadDictionaryKey: String {
|
||||
"\(type(of: Observation.self))_threadUpdatingValuesFlag"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Execute block without triggering events of changes made at defaults keys.
|
||||
|
||||
Example:
|
||||
Execute the closure without triggering change events.
|
||||
|
||||
Any `Defaults` key changes made within the closure will not propagate to `Defaults` event listeners (`Defaults.observe()` and `Defaults.publisher()`). This can be useful to prevent infinite recursion when you want to change a key in the callback listening to changes for the same key.
|
||||
|
||||
- Note: This only works with `Defaults.observe()` and `Defaults.publisher()`. User-made KVO will not be affected.
|
||||
|
||||
```
|
||||
let observer = Defaults.observe(keys: .key1, .key2) {
|
||||
// …
|
||||
|
||||
Defaults.withoutPropagation {
|
||||
// update some value at .key1
|
||||
// this will not be propagated
|
||||
// Update `.key1` without propagating the change to listeners.
|
||||
Defaults[.key1] = 11
|
||||
}
|
||||
// this will be propagated
|
||||
|
||||
// This will be propagated.
|
||||
Defaults[.someKey] = true
|
||||
}
|
||||
```
|
||||
|
||||
This only works with defaults `observe` or `publisher`. User made KVO will not be affected.
|
||||
*/
|
||||
public static func withoutPropagation(block: () -> Void) {
|
||||
public static func withoutPropagation(_ closure: () -> Void) {
|
||||
// How does it work?
|
||||
// KVO observation callbacks are executed right after change is made,
|
||||
// and run on the same thread as the caller. So it works by storing a flag in current
|
||||
// thread's dictionary, which is then evaluated in `observeValue` callback
|
||||
|
||||
let key = preventPropagationThreadDictKey
|
||||
// KVO observation callbacks are executed right after a change is made, and run on the same thread as the caller. So it works by storing a flag in the current thread's dictionary, which is then evaluated in the callback.
|
||||
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
Thread.current.threadDictionary[key] = true
|
||||
block()
|
||||
closure()
|
||||
Thread.current.threadDictionary[key] = false
|
||||
}
|
||||
|
||||
|
@ -235,8 +235,8 @@ extension Defaults {
|
|||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let key = preventPropagationThreadDictKey
|
||||
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
|
||||
guard !updatingValuesFlag else {
|
||||
return
|
||||
|
@ -245,66 +245,66 @@ extension Defaults {
|
|||
callback(BaseChange(change: change))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class CompositeUserDefaultsKeyObservation: NSObject, Observation {
|
||||
private static var observationContext = 0
|
||||
|
||||
|
||||
private final class SuiteKeyPair {
|
||||
weak var suite: UserDefaults?
|
||||
let key: String
|
||||
|
||||
|
||||
init(suite: UserDefaults, key: String) {
|
||||
self.suite = suite
|
||||
self.key = key
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var observables: [SuiteKeyPair]
|
||||
private var lifetimeAssociation: LifetimeAssociation? = nil
|
||||
private var lifetimeAssociation: LifetimeAssociation?
|
||||
private let callback: UserDefaultsKeyObservation.Callback
|
||||
|
||||
|
||||
init(observables: [(suite: UserDefaults, key: String)], callback: @escaping UserDefaultsKeyObservation.Callback) {
|
||||
self.observables = observables.map { SuiteKeyPair(suite: $0.suite, key: $0.key) }
|
||||
self.callback = callback
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
||||
public func start(options: ObservationOptions) {
|
||||
for observable in observables {
|
||||
observable.suite?.addObserver(
|
||||
self,
|
||||
forKeyPath: observable.key,
|
||||
options: options.toNSKeyValueObservingOptions,
|
||||
context: &type(of: self).observationContext
|
||||
context: &Self.observationContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func invalidate() {
|
||||
for observable in observables {
|
||||
observable.suite?.removeObserver(self, forKeyPath: observable.key, context: &type(of: self).observationContext)
|
||||
observable.suite?.removeObserver(self, forKeyPath: observable.key, context: &Self.observationContext)
|
||||
observable.suite = nil
|
||||
}
|
||||
|
||||
lifetimeAssociation?.cancel()
|
||||
}
|
||||
|
||||
|
||||
public func tieToLifetime(of weaklyHeldObject: AnyObject) -> Self {
|
||||
lifetimeAssociation = LifetimeAssociation(of: self, with: weaklyHeldObject, deinitHandler: { [weak self] in
|
||||
self?.invalidate()
|
||||
})
|
||||
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public func removeLifetimeTie() {
|
||||
lifetimeAssociation?.cancel()
|
||||
}
|
||||
|
||||
|
||||
// swiftlint:disable:next block_based_kvo
|
||||
override func observeValue(
|
||||
forKeyPath keyPath: String?,
|
||||
|
@ -313,29 +313,29 @@ extension Defaults {
|
|||
context: UnsafeMutableRawPointer?
|
||||
) {
|
||||
guard
|
||||
context == &type(of: self).observationContext
|
||||
context == &Self.observationContext
|
||||
else {
|
||||
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard
|
||||
object is UserDefaults,
|
||||
let change = change
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let key = preventPropagationThreadDictKey
|
||||
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
|
||||
if updatingValuesFlag {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
callback(BaseChange(change: change))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Observe a defaults key.
|
||||
|
||||
|
@ -399,10 +399,10 @@ extension Defaults {
|
|||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Observe multiple keys of any type, but without specific information about changes.
|
||||
|
||||
Observe multiple keys of any type, but without any information about the changes.
|
||||
|
||||
```
|
||||
extension Defaults.Keys {
|
||||
static let setting1 = Key<Bool>("setting1", default: false)
|
||||
|
@ -410,12 +410,12 @@ extension Defaults {
|
|||
}
|
||||
|
||||
let observer = Defaults.observe(keys: .setting1, .setting2) {
|
||||
//...
|
||||
// …
|
||||
}
|
||||
```
|
||||
*/
|
||||
public static func observe(
|
||||
keys: Keys...,
|
||||
keys: AnyKey...,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping () -> Void
|
||||
) -> Observation {
|
||||
|
@ -426,7 +426,7 @@ extension Defaults {
|
|||
handler()
|
||||
}
|
||||
compositeObservation.start(options: options)
|
||||
|
||||
|
||||
return compositeObservation
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,4 @@
|
|||
#if canImport(Combine)
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
|
@ -111,5 +110,4 @@ public struct Default<Value: Codable>: DynamicProperty {
|
|||
key.reset()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -369,7 +369,7 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
let publisher = Defaults
|
||||
.publisher(key)
|
||||
.compactMap { $0.newValue }
|
||||
.map(\.newValue)
|
||||
.eraseToAnyPublisher()
|
||||
.collect(2)
|
||||
|
||||
|
@ -452,12 +452,12 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
func testObserveMultipleKeys() {
|
||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||
let key2 = Defaults.Key<Bool>("observeKey2", default: true)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
var counter = 0
|
||||
observation = Defaults.observe(keys: key1, key2, options: []) {
|
||||
|
@ -468,20 +468,20 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Defaults[key1] = "y"
|
||||
Defaults[key2] = false
|
||||
observation.invalidate()
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
@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, *)
|
||||
func testObserveMultipleNSSecureKeys() {
|
||||
let key1 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let key2 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
var counter = 0
|
||||
observation = Defaults.observe(keys: key1, key2, options: []) {
|
||||
|
@ -492,7 +492,7 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
observation.invalidate()
|
||||
|
@ -535,7 +535,7 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
func testObservePreventPropagation() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
|
||||
let expect = expectation(description: "No infinite recursion")
|
||||
|
@ -556,7 +556,7 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
func testObservePreventPropagationMultipleKeys() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation1", default: nil)
|
||||
let key2 = Defaults.Key<Bool?>("preventPropagation2", default: nil)
|
||||
|
@ -578,15 +578,12 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
/**
|
||||
This checks if callback is still being called, if value is changed on second thread,
|
||||
while initial thread is doing some long lasting task.
|
||||
*/
|
||||
|
||||
// This checks if the callback is still being called if the value is changed on a second thread while the initial thread is doing some long running task.
|
||||
func testObservePreventPropagationMultipleThreads() {
|
||||
let key1 = Defaults.Key<Int?>("preventPropagation3", default: nil)
|
||||
let expect = expectation(description: "No infinite recursion")
|
||||
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key1, options: []) { _ in
|
||||
Defaults.withoutPropagation {
|
||||
|
@ -609,10 +606,8 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
/**
|
||||
Check if propagation prevention works across multiple observations
|
||||
*/
|
||||
|
||||
// Check if propagation prevention works across multiple observations.
|
||||
func testObservePreventPropagationMultipleObservations() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation4", default: nil)
|
||||
let key2 = Defaults.Key<Bool?>("preventPropagation5", default: nil)
|
||||
|
@ -627,14 +622,14 @@ final class DefaultsTests: XCTestCase {
|
|||
}
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
|
||||
Defaults[key1] = false
|
||||
observation1.invalidate()
|
||||
observation2.invalidate()
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
@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, *)
|
||||
func testObservePreventPropagationCombine() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation6", default: nil)
|
||||
|
@ -649,13 +644,13 @@ final class DefaultsTests: XCTestCase {
|
|||
}
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
|
||||
Defaults[key1] = false
|
||||
cancellable.cancel()
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
@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, *)
|
||||
func testObservePreventPropagationMultipleKeysCombine() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation7", default: nil)
|
||||
|
@ -671,18 +666,18 @@ final class DefaultsTests: XCTestCase {
|
|||
}
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
|
||||
Defaults[key2] = false
|
||||
cancellable.cancel()
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
@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, *)
|
||||
func testObservePreventPropagationModifiersCombine() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation9", default: nil)
|
||||
let expect = expectation(description: "No infinite recursion")
|
||||
|
||||
|
||||
var wasInside = false
|
||||
var cancellable: AnyCancellable!
|
||||
cancellable = Defaults.publisher(key1, options: [])
|
||||
|
@ -697,9 +692,9 @@ final class DefaultsTests: XCTestCase {
|
|||
expect.fulfill()
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
|
||||
Defaults[key1] = false
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
|
45
readme.md
45
readme.md
|
@ -239,23 +239,6 @@ Defaults[.isUnicornMode] = true
|
|||
|
||||
The observation will be valid until `self` is deinitialized.
|
||||
|
||||
### Control propagation of change events
|
||||
|
||||
```swift
|
||||
let observer = Defaults.observe(keys: .key1, .key2) {
|
||||
// …
|
||||
Defaults.withoutPropagation {
|
||||
// update some value at .key1
|
||||
// this will not be propagated
|
||||
Defaults[.key1] = 11
|
||||
}
|
||||
// this will be propagated
|
||||
Defaults[.someKey] = true
|
||||
}
|
||||
```
|
||||
|
||||
Changes made within `Defaults.withoutPropagation` block, will not be propagated to observation callbacks, and therefore will prevent infinite recursion.
|
||||
|
||||
### Reset keys to their default values
|
||||
|
||||
```swift
|
||||
|
@ -274,6 +257,24 @@ Defaults[.isUnicornMode]
|
|||
|
||||
This works for a `Key` with an optional too, which will be reset back to `nil`.
|
||||
|
||||
### Control propagation of change events
|
||||
|
||||
Changes made within the `Defaults.withoutPropagation` closure will not be propagated to observation callbacks (`Defaults.observe()` or `Defaults.publisher()`), and therefore could prevent infinite recursion.
|
||||
|
||||
```swift
|
||||
let observer = Defaults.observe(keys: .key1, .key2) {
|
||||
// …
|
||||
|
||||
Defaults.withoutPropagation {
|
||||
// Update `.key1` without propagating the change to listeners.
|
||||
Defaults[.key1] = 11
|
||||
}
|
||||
|
||||
// This will be propagated.
|
||||
Defaults[.someKey] = true
|
||||
}
|
||||
```
|
||||
|
||||
### It's just `UserDefaults` with sugar
|
||||
|
||||
This works too:
|
||||
|
@ -408,9 +409,9 @@ By default, it will also trigger an initial event on creation. This can be usefu
|
|||
|
||||
Type: `func`
|
||||
|
||||
Observe changes to multiple keys of any type, but without specific information about changes.
|
||||
Observe multiple keys of any type, but without any information about the changes.
|
||||
|
||||
Options same as in `observe` for a single key.
|
||||
Options are the same as in `.observe(…)` for a single key.
|
||||
|
||||
#### `Defaults.publisher(_ key:, options:)`
|
||||
|
||||
|
@ -500,11 +501,11 @@ Break the lifetime tie created by `tieToLifetime(of:)`, if one exists.
|
|||
|
||||
The effects of any call to `tieToLifetime(of:)` are reversed. Note however that if the tied-to object has already died, then the observation is already invalid and this method has no logical effect.
|
||||
|
||||
#### `Defaults.withoutPropagation(_ block:)`
|
||||
#### `Defaults.withoutPropagation(_ closure:)`
|
||||
|
||||
Execute block without emitting events of changes made at defaults keys.
|
||||
Execute the closure without triggering change events.
|
||||
|
||||
Changes made within the block will not be propagated to observation callbacks. This only works with defaults `observe` or `publisher`. User made KVO will not be affected.
|
||||
Any `Defaults` key changes made within the closure will not propagate to `Defaults` event listeners (`Defaults.observe()` and `Defaults.publisher()`). This can be useful to prevent infinite recursion when you want to change a key in the callback listening to changes for the same key.
|
||||
|
||||
### `@Default(_ key:)`
|
||||
|
||||
|
|
Loading…
Reference in New Issue