Support dynamic default value (#121)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
1b1a057220
commit
fbc67fd179
|
@ -41,7 +41,15 @@ public enum Defaults {
|
|||
}
|
||||
|
||||
public final class Key<Value: Serializable>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
/**
|
||||
It will be executed in these situations:
|
||||
|
||||
- `UserDefaults.object(forKey: string)` returns `nil`
|
||||
- A `bridge` cannot deserialize `Value` from `UserDefaults`
|
||||
*/
|
||||
private let defaultValueGetter: () -> Value
|
||||
|
||||
public var defaultValue: Value { defaultValueGetter() }
|
||||
|
||||
/**
|
||||
Create a defaults key.
|
||||
|
@ -51,7 +59,7 @@ public enum Defaults {
|
|||
The `default` parameter should not be used if the `Value` type is an optional.
|
||||
*/
|
||||
public init(_ key: String, default defaultValue: Value, suite: UserDefaults = .standard) {
|
||||
self.defaultValue = defaultValue
|
||||
self.defaultValueGetter = { defaultValue }
|
||||
|
||||
super.init(name: key, suite: suite)
|
||||
|
||||
|
@ -66,6 +74,25 @@ public enum Defaults {
|
|||
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
|
||||
suite.register(defaults: [name: serialized])
|
||||
}
|
||||
|
||||
/**
|
||||
Create a defaults key with a dynamic default value.
|
||||
|
||||
This can be useful in cases where you cannot define a static default value as it may change during the lifetime of the app.
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let camera = Key<AVCaptureDevice?>("camera") { .default(for: .video) }
|
||||
}
|
||||
```
|
||||
|
||||
- Note: This initializer will not set the default value in the actual `UserDefaults`. This should not matter much though. It's only really useful if you use legacy KVO bindings.
|
||||
*/
|
||||
public init(_ key: String, suite: UserDefaults = .standard, default defaultValueGetter: @escaping () -> Value) {
|
||||
self.defaultValueGetter = defaultValueGetter
|
||||
|
||||
super.init(name: key, suite: suite)
|
||||
}
|
||||
}
|
||||
|
||||
public static subscript<Value: Serializable>(key: Key<Value>) -> Value {
|
||||
|
|
|
@ -15,6 +15,8 @@ extension Defaults.Keys {
|
|||
static let data = Key<Data>("data", default: Data([]))
|
||||
static let date = Key<Date>("date", default: fixtureDate)
|
||||
static let uuid = Key<UUID?>("uuid")
|
||||
static let defaultDynamicDate = Key<Date>("defaultDynamicOptionalDate") { Date(timeIntervalSince1970: 0) }
|
||||
static let defaultDynamicOptionalDate = Key<Date?>("defaultDynamicOptionalDate") { Date(timeIntervalSince1970: 1) }
|
||||
}
|
||||
|
||||
final class DefaultsTests: XCTestCase {
|
||||
|
@ -54,6 +56,17 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTAssertEqual(Defaults[url], fixtureURL2)
|
||||
}
|
||||
|
||||
func testInitializeDynamicDateKey() {
|
||||
_ = Defaults.Key<Date>("independentInitializeDynamicDateKey") {
|
||||
XCTFail("Init dynamic key should not trigger getter")
|
||||
return Date()
|
||||
}
|
||||
_ = Defaults.Key<Date?>("independentInitializeDynamicOptionalDateKey") {
|
||||
XCTFail("Init dynamic optional key should not trigger getter")
|
||||
return Date()
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyRegistersDefault() {
|
||||
let keyName = "registersDefault"
|
||||
XCTAssertFalse(UserDefaults.standard.bool(forKey: keyName))
|
||||
|
@ -100,6 +113,27 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTAssertEqual(Defaults[.date], newDate)
|
||||
}
|
||||
|
||||
func testDynamicDateType() {
|
||||
XCTAssertEqual(Defaults[.defaultDynamicDate], Date(timeIntervalSince1970: 0))
|
||||
let next = Date(timeIntervalSince1970: 1)
|
||||
Defaults[.defaultDynamicDate] = next
|
||||
XCTAssertEqual(Defaults[.defaultDynamicDate], next)
|
||||
XCTAssertEqual(UserDefaults.standard.object(forKey: Defaults.Key<Date>.defaultDynamicDate.name) as! Date, next)
|
||||
Defaults.Key<Date>.defaultDynamicDate.reset()
|
||||
XCTAssertEqual(Defaults[.defaultDynamicDate], Date(timeIntervalSince1970: 0))
|
||||
}
|
||||
|
||||
func testDynamicOptionalDateType() {
|
||||
XCTAssertEqual(Defaults[.defaultDynamicOptionalDate], Date(timeIntervalSince1970: 1))
|
||||
let next = Date(timeIntervalSince1970: 2)
|
||||
Defaults[.defaultDynamicOptionalDate] = next
|
||||
XCTAssertEqual(Defaults[.defaultDynamicOptionalDate], next)
|
||||
XCTAssertEqual(UserDefaults.standard.object(forKey: Defaults.Key<Date>.defaultDynamicOptionalDate.name) as! Date, next)
|
||||
Defaults[.defaultDynamicOptionalDate] = nil
|
||||
XCTAssertEqual(Defaults[.defaultDynamicOptionalDate], Date(timeIntervalSince1970: 1))
|
||||
XCTAssertNil(UserDefaults.standard.object(forKey: Defaults.Key<Date>.defaultDynamicOptionalDate.name))
|
||||
}
|
||||
|
||||
func testFileURLType() {
|
||||
XCTAssertEqual(Defaults[.file], fixtureFileURL)
|
||||
}
|
||||
|
@ -188,6 +222,38 @@ final class DefaultsTests: XCTestCase {
|
|||
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 testDynamicOptionalDateTypeCombine() {
|
||||
let first = Date(timeIntervalSince1970: 0)
|
||||
let second = Date(timeIntervalSince1970: 1)
|
||||
let third = Date(timeIntervalSince1970: 2)
|
||||
let key = Defaults.Key<Date?>("combineDynamicOptionalDateKey") { first }
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValues: [(Date?, Date?)] = [(first, second), (second, third), (third, first)]
|
||||
|
||||
let cancellable = publisher.sink { actualValues in
|
||||
for (expected, actual) in zip(expectedValues, actualValues) {
|
||||
XCTAssertEqual(expected.0, actual.0)
|
||||
XCTAssertEqual(expected.1, actual.1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = second
|
||||
Defaults[key] = third
|
||||
Defaults.reset(key)
|
||||
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 testObserveMultipleKeysCombine() {
|
||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||
|
@ -321,6 +387,26 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDynamicOptionalDateKey() {
|
||||
let first = Date(timeIntervalSince1970: 0)
|
||||
let second = Date(timeIntervalSince1970: 1)
|
||||
let key = Defaults.Key<Date?>("observeDynamicOptionalDate") { first }
|
||||
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, first)
|
||||
XCTAssertEqual(change.newValue, second)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = second
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObservePreventPropagation() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
|
||||
let expect = expectation(description: "No infinite recursion")
|
||||
|
|
11
readme.md
11
readme.md
|
@ -123,6 +123,14 @@ if let name = Defaults[.name] {
|
|||
|
||||
The default value is then `nil`.
|
||||
|
||||
You can also specify a dynamic default value. This can be useful when the default value may change during the lifetime of the app:
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let camera = Key<AVCaptureDevice?>("camera") { .default(for: .video) }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Enum example
|
||||
|
@ -383,6 +391,9 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
|
|||
//=> true
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> A `Defaults.Key` with a dynamic default value will not register the default value in `UserDefaults`.
|
||||
|
||||
## API
|
||||
|
||||
### `Defaults`
|
||||
|
|
Loading…
Reference in New Issue