Support dynamic default value
This commit is contained in:
parent
be7e30ba36
commit
ef9a4f4484
|
@ -41,15 +41,24 @@ public enum Defaults {
|
|||
}
|
||||
|
||||
public final class Key<Value: Serializable>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
public typealias DefaultGetter = () -> Value
|
||||
/**
|
||||
The `defaultGetter` will be executed in these situations:
|
||||
|
||||
- `UserDefaults.object(forKey: string)` return `nil`
|
||||
- `bridge` cannot deserialize `Value` from `UserDefaults`
|
||||
*/
|
||||
private let defaultGetter: DefaultGetter
|
||||
public var defaultValue: Value { defaultGetter() }
|
||||
|
||||
/**
|
||||
Create a defaults key.
|
||||
|
||||
The `default` parameter can be left out if the `Value` type is an optional.
|
||||
*/
|
||||
public init(_ key: String, default defaultValue: Value, suite: UserDefaults = .standard) {
|
||||
self.defaultValue = defaultValue
|
||||
public init(_ key: String, default defaultGetter: @autoclosure @escaping DefaultGetter, suite: UserDefaults = .standard) {
|
||||
let defaultValue = defaultGetter()
|
||||
self.defaultGetter = defaultGetter
|
||||
|
||||
super.init(name: key, suite: suite)
|
||||
|
||||
|
@ -64,6 +73,17 @@ 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 dynamic getter.
|
||||
|
||||
- NOTE: If using `defaultGetter`, it will not set the default value in the actual UserDefaults.
|
||||
*/
|
||||
public init(_ key: String, default defaultGetter: @escaping DefaultGetter, suite: UserDefaults = .standard) {
|
||||
self.defaultGetter = defaultGetter
|
||||
|
||||
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")
|
||||
|
|
12
readme.md
12
readme.md
|
@ -123,6 +123,15 @@ if let name = Defaults[.name] {
|
|||
|
||||
The default value is then `nil`.
|
||||
|
||||
Sometimes you cannot define a static default value as it may change during the lifetime of the app.
|
||||
`Defaults.Key` also support dynamic default value.
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let camera = Key<AVCaptureDevice?>("camera") { .default(for: .video) }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Enum example
|
||||
|
@ -383,6 +392,9 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
|
|||
//=> true
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> `Defaults.Key` with dynamic default value will not register the `default` value in `UserDefaults`.
|
||||
|
||||
## API
|
||||
|
||||
### `Defaults`
|
||||
|
|
Loading…
Reference in New Issue