Support dynamic default value (#121)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
hank121314 2022-11-17 18:36:29 +08:00 committed by GitHub
parent 1b1a057220
commit fbc67fd179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 2 deletions

View File

@ -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 {

View File

@ -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")

View File

@ -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`