Switch to static subscript on `Defaults`

This commit is contained in:
Sindre Sorhus 2019-09-11 11:59:28 +07:00
parent d1e42154f9
commit 90ac6f8802
6 changed files with 213 additions and 154 deletions

View File

@ -8,7 +8,7 @@ Pod::Spec.new do |s|
s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' } s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' }
s.source = { :git => 'https://github.com/sindresorhus/Defaults.git', :tag => "v#{s.version}" } s.source = { :git => 'https://github.com/sindresorhus/Defaults.git', :tag => "v#{s.version}" }
s.source_files = 'Sources/**/*.swift' s.source_files = 'Sources/**/*.swift'
s.swift_version = '5.0' s.swift_version = '5.1'
s.macos.deployment_target = '10.12' s.macos.deployment_target = '10.12'
s.ios.deployment_target = '10.0' s.ios.deployment_target = '10.0'
s.tvos.deployment_target = '10.0' s.tvos.deployment_target = '10.0'

View File

@ -8,7 +8,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
52D6D9871BEFF229002C0205 /* Defaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Defaults.framework */; }; 52D6D9871BEFF229002C0205 /* Defaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Defaults.framework */; };
6614F6E322FC6E1C00B0C9CE /* readme.md in Resources */ = {isa = PBXBuildFile; fileRef = 6614F6E222FC6E1C00B0C9CE /* readme.md */; };
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; }; 8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; }; 8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; }; 8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
@ -58,7 +57,7 @@
52D6D9E21BEFFF6E002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9E21BEFFF6E002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52D6D9F01BEFFFBE002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9F01BEFFFBE002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52D6DA0F1BF000BD002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6DA0F1BF000BD002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6614F6E222FC6E1C00B0C9CE /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; }; 6614F6E222FC6E1C00B0C9CE /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = readme.md; sourceTree = "<group>"; usesTabs = 1; };
8933C7841EB5B820000D00A4 /* Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Defaults.swift; sourceTree = "<group>"; usesTabs = 1; }; 8933C7841EB5B820000D00A4 /* Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Defaults.swift; sourceTree = "<group>"; usesTabs = 1; };
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DefaultsTests.swift; sourceTree = "<group>"; usesTabs = 1; }; 8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DefaultsTests.swift; sourceTree = "<group>"; usesTabs = 1; };
AD2FAA261CD0B6D800659CF4 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; }; AD2FAA261CD0B6D800659CF4 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
@ -460,7 +459,6 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
6614F6E322FC6E1C00B0C9CE /* readme.md in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -621,7 +619,7 @@
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = singlefile; SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = ""; VERSION_INFO_PREFIX = "";
@ -674,7 +672,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.12; MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -706,7 +704,6 @@
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile; SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -733,7 +730,6 @@
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -754,7 +750,6 @@
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_COMPILATION_MODE = singlefile; SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -775,7 +770,6 @@
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -799,7 +793,6 @@
PRODUCT_NAME = Defaults; PRODUCT_NAME = Defaults;
SDKROOT = watchos; SDKROOT = watchos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4; TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 3.0; WATCHOS_DEPLOYMENT_TARGET = 3.0;
}; };
@ -827,7 +820,6 @@
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4; TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 3.0; WATCHOS_DEPLOYMENT_TARGET = 3.0;
}; };
@ -853,7 +845,6 @@
PRODUCT_NAME = Defaults; PRODUCT_NAME = Defaults;
SDKROOT = appletvos; SDKROOT = appletvos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3; TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 10.0; TVOS_DEPLOYMENT_TARGET = 10.0;
}; };
@ -881,7 +872,6 @@
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3; TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 10.0; TVOS_DEPLOYMENT_TARGET = 10.0;
}; };
@ -910,7 +900,6 @@
PRODUCT_NAME = Defaults; PRODUCT_NAME = Defaults;
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -939,7 +928,6 @@
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -957,7 +945,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.Defaults.Defaults-macOS-Tests"; PRODUCT_BUNDLE_IDENTIFIER = "com.Defaults.Defaults-macOS-Tests";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -977,7 +964,6 @@
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -996,7 +982,6 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 10.0; TVOS_DEPLOYMENT_TARGET = 10.0;
}; };
name = Debug; name = Debug;
@ -1018,7 +1003,6 @@
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 10.0; TVOS_DEPLOYMENT_TARGET = 10.0;
}; };
name = Release; name = Release;

View File

@ -14,6 +14,7 @@ public final class Defaults {
public let defaultValue: T public let defaultValue: T
public let suite: UserDefaults public let suite: UserDefaults
/// Create a defaults key.
public init(_ key: String, default defaultValue: T, suite: UserDefaults = .standard) { public init(_ key: String, default defaultValue: T, suite: UserDefaults = .standard) {
self.name = key self.name = key
self.defaultValue = defaultValue self.defaultValue = defaultValue
@ -34,6 +35,7 @@ public final class Defaults {
public let name: String public let name: String
public let suite: UserDefaults public let suite: UserDefaults
/// Create an optional defaults key.
public init(_ key: String, suite: UserDefaults = .standard) { public init(_ key: String, suite: UserDefaults = .standard) {
self.name = key self.name = key
self.suite = suite self.suite = suite
@ -42,65 +44,129 @@ public final class Defaults {
fileprivate init() {} fileprivate init() {}
public subscript<T: Codable>(key: Defaults.Key<T>) -> T { /// Access a defaults value using a `Defaults.Key`.
get { public static subscript<T: Codable>(key: Defaults.Key<T>) -> T {
return key.suite[key] get { key.suite[key] }
}
set { set {
key.suite[key] = newValue key.suite[key] = newValue
} }
} }
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? { /// Access a defaults value using a `Defaults.OptionalKey`.
get { public static subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
return key.suite[key] get { key.suite[key] }
}
set { set {
key.suite[key] = newValue key.suite[key] = newValue
} }
} }
public func clear(suite: UserDefaults = .standard) {
for key in suite.dictionaryRepresentation().keys {
suite.removeObject(forKey: key)
}
}
/// Reset keys back to their default values. /**
/// - Parameter keys: Keys to reset. Reset the given keys back to their default values.
/// - Parameter suite: `UserDefaults` suite.
public func reset<T: Codable>(_ keys: Defaults.Key<T>..., suite: UserDefaults = .standard) { - Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
}
Defaults[.isUnicornMode] = true
//=> true
Defaults.reset(.isUnicornMode)
Defaults[.isUnicornMode]
//=> false
```
*/
public static func reset<T: Codable>(_ keys: Defaults.Key<T>..., suite: UserDefaults = .standard) {
reset(keys, suite: suite) reset(keys, suite: suite)
} }
/// Reset an array of keys back to their default values. /**
/// - Parameter keys: Keys to reset. Reset the given array of keys back to their default values.
/// - Parameter suite: `UserDefaults` suite.
public func reset<T: Codable>(_ keys: [Defaults.Key<T>], suite: UserDefaults = .standard) { - Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
}
Defaults[.isUnicornMode] = true
//=> true
Defaults.reset(.isUnicornMode)
Defaults[.isUnicornMode]
//=> false
```
*/
public static func reset<T: Codable>(_ keys: [Defaults.Key<T>], suite: UserDefaults = .standard) {
for key in keys { for key in keys {
key.suite[key] = key.defaultValue key.suite[key] = key.defaultValue
} }
} }
/// Reset optional keys back to `nil`. /**
/// - Parameter keys: Keys to reset. Reset the given optional keys back to `nil`.
/// - Parameter suite: `UserDefaults` suite.
public func reset<T: Codable>(_ keys: Defaults.OptionalKey<T>..., suite: UserDefaults = .standard) { - Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
extension Defaults.Keys {
static let unicorn = OptionalKey<String>("unicorn")
}
Defaults[.unicorn] = "🦄"
Defaults.reset(.unicorn)
Defaults[.unicorn]
//=> nil
```
*/
public static func reset<T: Codable>(_ keys: Defaults.OptionalKey<T>..., suite: UserDefaults = .standard) {
reset(keys, suite: suite) reset(keys, suite: suite)
} }
/// Reset an array of optional keys back to `nil`. /**
/// - Parameter keys: Keys to reset. Reset the given array of optional keys back to `nil`.
/// - Parameter suite: `UserDefaults` suite.
public func reset<T: Codable>(_ keys: [Defaults.OptionalKey<T>], suite: UserDefaults = .standard) { - Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
extension Defaults.Keys {
static let unicorn = OptionalKey<String>("unicorn")
}
Defaults[.unicorn] = "🦄"
Defaults.reset(.unicorn)
Defaults[.unicorn]
//=> nil
```
*/
public static func reset<T: Codable>(_ keys: [Defaults.OptionalKey<T>], suite: UserDefaults = .standard) {
for key in keys { for key in keys {
key.suite[key] = nil key.suite[key] = nil
} }
} }
}
// Has to be `defaults` lowercase until Swift supports static subscripts /**
public let defaults = Defaults() Remove all entries from the `UserDefaults` suite.
*/
public static func clear(suite: UserDefaults = .standard) {
for key in suite.dictionaryRepresentation().keys {
suite.removeObject(forKey: key)
}
}
}
extension UserDefaults { extension UserDefaults {
private func _get<T: Codable>(_ key: String) -> T? { private func _get<T: Codable>(_ key: String) -> T? {
@ -147,18 +213,14 @@ extension UserDefaults {
} }
public subscript<T: Codable>(key: Defaults.Key<T>) -> T { public subscript<T: Codable>(key: Defaults.Key<T>) -> T {
get { get { _get(key.name) ?? key.defaultValue }
return _get(key.name) ?? key.defaultValue
}
set { set {
_set(key.name, to: newValue) _set(key.name, to: newValue)
} }
} }
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? { public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
get { get { _get(key.name) }
return _get(key.name)
}
set { set {
guard let value = newValue else { guard let value = newValue else {
set(nil, forKey: key.name) set(nil, forKey: key.name)

View File

@ -117,20 +117,20 @@ extension Defaults {
} }
/** /**
Observe a defaults key Observe a defaults key.
``` ```
extension Defaults.Keys { extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false) static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
} }
let observer = defaults.observe(.isUnicornMode) { change in let observer = Defaults.observe(.isUnicornMode) { change in
print(change.newValue) print(change.newValue)
//=> false //=> false
} }
``` ```
*/ */
public func observe<T: Codable>( public static func observe<T: Codable>(
_ key: Defaults.Key<T>, _ key: Defaults.Key<T>,
options: NSKeyValueObservingOptions = [.initial, .old, .new], options: NSKeyValueObservingOptions = [.initial, .old, .new],
handler: @escaping (KeyChange<T>) -> Void handler: @escaping (KeyChange<T>) -> Void
@ -145,20 +145,20 @@ extension Defaults {
} }
/** /**
Observe an optional defaults key Observe an optional defaults key.
``` ```
extension Defaults.Keys { extension Defaults.Keys {
static let isUnicornMode = OptionalKey<Bool>("isUnicornMode") static let isUnicornMode = OptionalKey<Bool>("isUnicornMode")
} }
let observer = defaults.observe(.isUnicornMode) { change in let observer = Defaults.observe(.isUnicornMode) { change in
print(change.newValue) print(change.newValue)
//=> Optional(nil) //=> Optional(nil)
} }
``` ```
*/ */
public func observe<T: Codable>( public static func observe<T: Codable>(
_ key: Defaults.OptionalKey<T>, _ key: Defaults.OptionalKey<T>,
options: NSKeyValueObservingOptions = [.initial, .old, .new], options: NSKeyValueObservingOptions = [.initial, .old, .new],
handler: @escaping (OptionalKeyChange<T>) -> Void handler: @escaping (OptionalKeyChange<T>) -> Void

View File

@ -24,30 +24,30 @@ extension Defaults.Keys {
final class DefaultsTests: XCTestCase { final class DefaultsTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
defaults.clear() Defaults.clear()
} }
override func tearDown() { override func tearDown() {
super.setUp() super.setUp()
defaults.clear() Defaults.clear()
} }
func testKey() { func testKey() {
let key = Defaults.Key<Bool>("independentKey", default: false) let key = Defaults.Key<Bool>("independentKey", default: false)
XCTAssertFalse(defaults[key]) XCTAssertFalse(Defaults[key])
defaults[key] = true Defaults[key] = true
XCTAssertTrue(defaults[key]) XCTAssertTrue(Defaults[key])
} }
func testOptionalKey() { func testOptionalKey() {
let key = Defaults.OptionalKey<Bool>("independentOptionalKey") let key = Defaults.OptionalKey<Bool>("independentOptionalKey")
XCTAssertNil(defaults[key]) XCTAssertNil(Defaults[key])
defaults[key] = true Defaults[key] = true
XCTAssertTrue(defaults[key]!) XCTAssertTrue(Defaults[key]!)
defaults[key] = nil Defaults[key] = nil
XCTAssertNil(defaults[key]) XCTAssertNil(Defaults[key])
defaults[key] = false Defaults[key] = false
XCTAssertFalse(defaults[key]!) XCTAssertFalse(Defaults[key]!)
} }
func testKeyRegistersDefault() { func testKeyRegistersDefault() {
@ -56,7 +56,7 @@ final class DefaultsTests: XCTestCase {
_ = Defaults.Key<Bool>(keyName, default: true) _ = Defaults.Key<Bool>(keyName, default: true)
XCTAssertEqual(UserDefaults.standard.bool(forKey: keyName), true) XCTAssertEqual(UserDefaults.standard.bool(forKey: keyName), true)
// Test that it works with multiple keys with defaults // Test that it works with multiple keys with Defaults.
let keyName2 = "registersDefault2" let keyName2 = "registersDefault2"
_ = Defaults.Key<String>(keyName2, default: keyName2) _ = Defaults.Key<String>(keyName2, default: keyName2)
XCTAssertEqual(UserDefaults.standard.string(forKey: keyName2), keyName2) XCTAssertEqual(UserDefaults.standard.string(forKey: keyName2), keyName2)
@ -70,56 +70,56 @@ final class DefaultsTests: XCTestCase {
} }
func testKeys() { func testKeys() {
XCTAssertFalse(defaults[.key]) XCTAssertFalse(Defaults[.key])
defaults[.key] = true Defaults[.key] = true
XCTAssertTrue(defaults[.key]) XCTAssertTrue(Defaults[.key])
} }
func testUrlType() { func testUrlType() {
XCTAssertEqual(defaults[.url], fixtureURL) XCTAssertEqual(Defaults[.url], fixtureURL)
let newUrl = URL(string: "https://twitter.com")! let newUrl = URL(string: "https://twitter.com")!
defaults[.url] = newUrl Defaults[.url] = newUrl
XCTAssertEqual(defaults[.url], newUrl) XCTAssertEqual(Defaults[.url], newUrl)
} }
func testEnumType() { func testEnumType() {
XCTAssertEqual(defaults[.enum], FixtureEnum.oneHour) XCTAssertEqual(Defaults[.enum], FixtureEnum.oneHour)
} }
func testDataType() { func testDataType() {
XCTAssertEqual(defaults[.data], Data([])) XCTAssertEqual(Defaults[.data], Data([]))
let newData = Data([0xFF]) let newData = Data([0xFF])
defaults[.data] = newData Defaults[.data] = newData
XCTAssertEqual(defaults[.data], newData) XCTAssertEqual(Defaults[.data], newData)
} }
func testDateType() { func testDateType() {
XCTAssertEqual(defaults[.date], fixtureDate) XCTAssertEqual(Defaults[.date], fixtureDate)
let newDate = Date() let newDate = Date()
defaults[.date] = newDate Defaults[.date] = newDate
XCTAssertEqual(defaults[.date], newDate) XCTAssertEqual(Defaults[.date], newDate)
} }
func testClear() { func testClear() {
let key = Defaults.Key<Bool>("clear", default: false) let key = Defaults.Key<Bool>("clear", default: false)
defaults[key] = true Defaults[key] = true
XCTAssertTrue(defaults[key]) XCTAssertTrue(Defaults[key])
defaults.clear() Defaults.clear()
XCTAssertFalse(defaults[key]) XCTAssertFalse(Defaults[key])
} }
func testCustomSuite() { func testCustomSuite() {
let customSuite = UserDefaults(suiteName: "com.sindresorhus.customSuite")! let customSuite = UserDefaults(suiteName: "com.sindresorhus.customSuite")!
let key = Defaults.Key<Bool>("customSuite", default: false, suite: customSuite) let key = Defaults.Key<Bool>("customSuite", default: false, suite: customSuite)
XCTAssertFalse(customSuite[key]) XCTAssertFalse(customSuite[key])
XCTAssertFalse(defaults[key]) XCTAssertFalse(Defaults[key])
defaults[key] = true Defaults[key] = true
XCTAssertTrue(customSuite[key]) XCTAssertTrue(customSuite[key])
XCTAssertTrue(defaults[key]) XCTAssertTrue(Defaults[key])
defaults.clear(suite: customSuite) Defaults.clear(suite: customSuite)
} }
func testObserveKey() { func testObserveKey() {
@ -127,14 +127,14 @@ final class DefaultsTests: XCTestCase {
let expect = expectation(description: "Observation closure being called") let expect = expectation(description: "Observation closure being called")
var observation: DefaultsObservation! var observation: DefaultsObservation!
observation = defaults.observe(key, options: [.old, .new]) { change in observation = Defaults.observe(key, options: [.old, .new]) { change in
XCTAssertFalse(change.oldValue) XCTAssertFalse(change.oldValue)
XCTAssertTrue(change.newValue) XCTAssertTrue(change.newValue)
observation.invalidate() observation.invalidate()
expect.fulfill() expect.fulfill()
} }
defaults[key] = true Defaults[key] = true
waitForExpectations(timeout: 10) waitForExpectations(timeout: 10)
} }
@ -144,14 +144,14 @@ final class DefaultsTests: XCTestCase {
let expect = expectation(description: "Observation closure being called") let expect = expectation(description: "Observation closure being called")
var observation: DefaultsObservation! var observation: DefaultsObservation!
observation = defaults.observe(key, options: [.old, .new]) { change in observation = Defaults.observe(key, options: [.old, .new]) { change in
XCTAssertNil(change.oldValue) XCTAssertNil(change.oldValue)
XCTAssertTrue(change.newValue!) XCTAssertTrue(change.newValue!)
observation.invalidate() observation.invalidate()
expect.fulfill() expect.fulfill()
} }
defaults[key] = true Defaults[key] = true
waitForExpectations(timeout: 10) waitForExpectations(timeout: 10)
} }
@ -163,14 +163,14 @@ final class DefaultsTests: XCTestCase {
let expect = expectation(description: "Observation closure being called") let expect = expectation(description: "Observation closure being called")
var observation: DefaultsObservation! var observation: DefaultsObservation!
observation = defaults.observe(key, options: [.old, .new]) { change in observation = Defaults.observe(key, options: [.old, .new]) { change in
XCTAssertEqual(change.oldValue, fixtureURL) XCTAssertEqual(change.oldValue, fixtureURL)
XCTAssertEqual(change.newValue, fixtureURL2) XCTAssertEqual(change.newValue, fixtureURL2)
observation.invalidate() observation.invalidate()
expect.fulfill() expect.fulfill()
} }
defaults[key] = fixtureURL2 Defaults[key] = fixtureURL2
waitForExpectations(timeout: 10) waitForExpectations(timeout: 10)
} }
@ -180,14 +180,14 @@ final class DefaultsTests: XCTestCase {
let expect = expectation(description: "Observation closure being called") let expect = expectation(description: "Observation closure being called")
var observation: DefaultsObservation! var observation: DefaultsObservation!
observation = defaults.observe(key, options: [.old, .new]) { change in observation = Defaults.observe(key, options: [.old, .new]) { change in
XCTAssertEqual(change.oldValue, .oneHour) XCTAssertEqual(change.oldValue, .oneHour)
XCTAssertEqual(change.newValue, .tenMinutes) XCTAssertEqual(change.newValue, .tenMinutes)
observation.invalidate() observation.invalidate()
expect.fulfill() expect.fulfill()
} }
defaults[key] = .tenMinutes Defaults[key] = .tenMinutes
waitForExpectations(timeout: 10) waitForExpectations(timeout: 10)
} }
@ -199,11 +199,11 @@ final class DefaultsTests: XCTestCase {
let newString2 = "bar2" let newString2 = "bar2"
let key1 = Defaults.Key<String>("key1", default: defaultString1) let key1 = Defaults.Key<String>("key1", default: defaultString1)
let key2 = Defaults.Key<String>("key2", default: defaultString2) let key2 = Defaults.Key<String>("key2", default: defaultString2)
defaults[key1] = newString1 Defaults[key1] = newString1
defaults[key2] = newString2 Defaults[key2] = newString2
defaults.reset(key1) Defaults.reset(key1)
XCTAssertEqual(defaults[key1], defaultString1) XCTAssertEqual(Defaults[key1], defaultString1)
XCTAssertEqual(defaults[key2], newString2) XCTAssertEqual(Defaults[key2], newString2)
} }
func testResetKeyArray() { func testResetKeyArray() {
@ -216,13 +216,13 @@ final class DefaultsTests: XCTestCase {
let key1 = Defaults.Key<String>("akey1", default: defaultString1) let key1 = Defaults.Key<String>("akey1", default: defaultString1)
let key2 = Defaults.Key<String>("akey2", default: defaultString2) let key2 = Defaults.Key<String>("akey2", default: defaultString2)
let key3 = Defaults.Key<String>("akey3", default: defaultString3) let key3 = Defaults.Key<String>("akey3", default: defaultString3)
defaults[key1] = newString1 Defaults[key1] = newString1
defaults[key2] = newString2 Defaults[key2] = newString2
defaults[key3] = newString3 Defaults[key3] = newString3
defaults.reset(key1, key2) Defaults.reset(key1, key2)
XCTAssertEqual(defaults[key1], defaultString1) XCTAssertEqual(Defaults[key1], defaultString1)
XCTAssertEqual(defaults[key2], defaultString2) XCTAssertEqual(Defaults[key2], defaultString2)
XCTAssertEqual(defaults[key3], newString3) XCTAssertEqual(Defaults[key3], newString3)
} }
func testResetOptionalKey() { func testResetOptionalKey() {
@ -230,11 +230,11 @@ final class DefaultsTests: XCTestCase {
let newString2 = "bar2" let newString2 = "bar2"
let key1 = Defaults.OptionalKey<String>("optionalKey1") let key1 = Defaults.OptionalKey<String>("optionalKey1")
let key2 = Defaults.OptionalKey<String>("optionalKey2") let key2 = Defaults.OptionalKey<String>("optionalKey2")
defaults[key1] = newString1 Defaults[key1] = newString1
defaults[key2] = newString2 Defaults[key2] = newString2
defaults.reset(key1) Defaults.reset(key1)
XCTAssertEqual(defaults[key1], nil) XCTAssertEqual(Defaults[key1], nil)
XCTAssertEqual(defaults[key2], newString2) XCTAssertEqual(Defaults[key2], newString2)
} }
func testResetOptionalKeyArray() { func testResetOptionalKeyArray() {
@ -244,12 +244,12 @@ final class DefaultsTests: XCTestCase {
let key1 = Defaults.OptionalKey<String>("aoptionalKey1") let key1 = Defaults.OptionalKey<String>("aoptionalKey1")
let key2 = Defaults.OptionalKey<String>("aoptionalKey2") let key2 = Defaults.OptionalKey<String>("aoptionalKey2")
let key3 = Defaults.OptionalKey<String>("aoptionalKey3") let key3 = Defaults.OptionalKey<String>("aoptionalKey3")
defaults[key1] = newString1 Defaults[key1] = newString1
defaults[key2] = newString2 Defaults[key2] = newString2
defaults[key3] = newString3 Defaults[key3] = newString3
defaults.reset(key1, key2) Defaults.reset(key1, key2)
XCTAssertEqual(defaults[key1], nil) XCTAssertEqual(Defaults[key1], nil)
XCTAssertEqual(defaults[key2], nil) XCTAssertEqual(Defaults[key2], nil)
XCTAssertEqual(defaults[key3], newString3) XCTAssertEqual(Defaults[key3], newString3)
} }
} }

View File

@ -2,7 +2,7 @@
> Swifty and modern [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) > Swifty and modern [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults)
This package is used in production by the [Lungo](https://sindresorhus.com/lungo), [Battery Indicator](https://sindresorhus.com/battery-indicator), and [HEIC Converter](https://sindresorhus.com/heic-converter) app. This package is used in production by the [Gifski](https://github.com/sindresorhus/Gifski), [Lungo](https://sindresorhus.com/lungo), [Battery Indicator](https://sindresorhus.com/battery-indicator), and [HEIC Converter](https://sindresorhus.com/heic-converter) app.
## Highlights ## Highlights
@ -58,19 +58,19 @@ extension Defaults.Keys {
} }
``` ```
You can then access it as a subscript on the `defaults` global (note lowercase): You can then access it as a subscript on the `Defaults` global:
```swift ```swift
defaults[.quality] Defaults[.quality]
//=> 0.8 //=> 0.8
defaults[.quality] = 0.5 Defaults[.quality] = 0.5
//=> 0.5 //=> 0.5
defaults[.quality] += 0.1 Defaults[.quality] += 0.1
//=> 0.6 //=> 0.6
defaults[.quality] = "🦄" Defaults[.quality] = "🦄"
//=> [Cannot assign value of type 'String' to type 'Double'] //=> [Cannot assign value of type 'String' to type 'Double']
``` ```
@ -81,7 +81,7 @@ extension Defaults.Keys {
static let name = OptionalKey<Double>("name") static let name = OptionalKey<Double>("name")
} }
if let name = defaults[.name] { if let name = Defaults[.name] {
print(name) print(name)
} }
``` ```
@ -101,7 +101,7 @@ extension Defaults.Keys {
static let defaultDuration = Key<DurationKeys>("defaultDuration", default: .oneHour) static let defaultDuration = Key<DurationKeys>("defaultDuration", default: .oneHour)
} }
defaults[.defaultDuration].rawValue Defaults[.defaultDuration].rawValue
//=> "1 Hour" //=> "1 Hour"
``` ```
@ -127,7 +127,7 @@ extension Defaults.Keys {
static let isUnicorn = Key<Bool>("isUnicorn", default: true, suite: extensionDefaults) static let isUnicorn = Key<Bool>("isUnicorn", default: true, suite: extensionDefaults)
} }
defaults[.isUnicorn] Defaults[.isUnicorn]
//=> true //=> true
// Or // Or
@ -143,7 +143,7 @@ You are not required to attach keys to `Defaults.Keys`.
```swift ```swift
let isUnicorn = Defaults.Key<Bool>("isUnicorn", default: true) let isUnicorn = Defaults.Key<Bool>("isUnicorn", default: true)
defaults[isUnicorn] Defaults[isUnicorn]
//=> true //=> true
``` ```
@ -154,7 +154,7 @@ extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false) static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
} }
let observer = defaults.observe(.isUnicornMode) { change in let observer = Defaults.observe(.isUnicornMode) { change in
// Initial event // Initial event
print(change.oldValue) print(change.oldValue)
//=> false //=> false
@ -168,7 +168,7 @@ let observer = defaults.observe(.isUnicornMode) { change in
//=> true //=> true
} }
defaults[.isUnicornMode] = true Defaults[.isUnicornMode] = true
``` ```
In contrast to the native `UserDefaults` key observation, here you receive a strongly-typed change object. In contrast to the native `UserDefaults` key observation, here you receive a strongly-typed change object.
@ -180,12 +180,12 @@ extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false) static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
} }
defaults[.isUnicornMode] = true Defaults[.isUnicornMode] = true
//=> true //=> true
defaults.reset(.isUnicornMode) Defaults.reset(.isUnicornMode)
defaults[.isUnicornMode] Defaults[.isUnicornMode]
//=> false //=> false
``` ```
@ -207,7 +207,7 @@ print(UserDefaults.standard.bool(forKey: isUnicornMode.name))
## API ## API
### `let defaults = Defaults()` ### `Defaults`
#### `Defaults.Keys` #### `Defaults.Keys`
@ -225,7 +225,7 @@ Type: `class`
Create a key with a default value. Create a key with a default value.
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with Interface Builder binding. The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
#### `Defaults.OptionalKey` *(alias `Defaults.Keys.OptionalKey`)* #### `Defaults.OptionalKey` *(alias `Defaults.Keys.OptionalKey`)*
@ -237,20 +237,23 @@ Type: `class`
Create a key with an optional value. Create a key with an optional value.
#### `Defaults#clear` #### `Defaults.reset`
```swift ```swift
clear(suite: UserDefaults = .standard) Defaults.reset<T: Codable>(_ keys: Defaults.Key<T>..., suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: [Defaults.Key<T>], suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: Defaults.OptionalKey<T>..., suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: [Defaults.OptionalKey<T>], suite: UserDefaults = .standard)
``` ```
Type: `func` Type: `func`
Clear the user defaults. Reset the given keys back to their default values.
#### `Defaults#observe` #### `Defaults.observe`
```swift ```swift
observe<T: Codable>( Defaults.observe<T: Codable>(
_ key: Defaults.Key<T>, _ key: Defaults.Key<T>,
options: NSKeyValueObservingOptions = [.initial, .old, .new], options: NSKeyValueObservingOptions = [.initial, .old, .new],
handler: @escaping (KeyChange<T>) -> Void handler: @escaping (KeyChange<T>) -> Void
@ -258,7 +261,7 @@ observe<T: Codable>(
``` ```
```swift ```swift
observe<T: Codable>( Defaults.observe<T: Codable>(
_ key: Defaults.OptionalKey<T>, _ key: Defaults.OptionalKey<T>,
options: NSKeyValueObservingOptions = [.initial, .old, .new], options: NSKeyValueObservingOptions = [.initial, .old, .new],
handler: @escaping (OptionalKeyChange<T>) -> Void handler: @escaping (OptionalKeyChange<T>) -> Void
@ -271,6 +274,16 @@ Observe changes to a key or an optional key.
By default, it will also trigger an initial event on creation. This can be useful for setting default values on controls. You can override this behavior with the `options` argument. By default, it will also trigger an initial event on creation. This can be useful for setting default values on controls. You can override this behavior with the `options` argument.
#### `Defaults.clear`
```swift
Defaults.clear(suite: UserDefaults = .standard)
```
Type: `func`
Clear the user defaults.
## FAQ ## FAQ