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,6 +15,7 @@ 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
|
||||
|
|
|
@ -141,38 +141,38 @@ extension Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Example:
|
||||
```
|
||||
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
|
||||
// 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 = preventPropagationThreadDictKey
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
Thread.current.threadDictionary[key] = true
|
||||
block()
|
||||
closure()
|
||||
Thread.current.threadDictionary[key] = false
|
||||
}
|
||||
|
||||
|
@ -236,7 +236,7 @@ extension Defaults {
|
|||
return
|
||||
}
|
||||
|
||||
let key = preventPropagationThreadDictKey
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
|
||||
guard !updatingValuesFlag else {
|
||||
return
|
||||
|
@ -260,7 +260,7 @@ extension Defaults {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -279,14 +279,14 @@ extension Defaults {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,7 @@ 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
|
||||
|
@ -326,7 +326,7 @@ extension Defaults {
|
|||
return
|
||||
}
|
||||
|
||||
let key = preventPropagationThreadDictKey
|
||||
let key = preventPropagationThreadDictionaryKey
|
||||
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
|
||||
if updatingValuesFlag {
|
||||
return
|
||||
|
@ -401,7 +401,7 @@ extension Defaults {
|
|||
}
|
||||
|
||||
/**
|
||||
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 {
|
||||
|
@ -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 {
|
||||
|
|
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)
|
||||
|
||||
|
@ -579,10 +579,7 @@ 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")
|
||||
|
@ -610,9 +607,7 @@ 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)
|
||||
|
|
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