Fix `Defaults.reset()`

This commit is contained in:
Sindre Sorhus 2020-04-17 22:32:44 +08:00
parent 77c05abe10
commit 15c096d7fd
9 changed files with 1335 additions and 313 deletions

View File

@ -21,6 +21,14 @@
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; }; E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; };
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; }; E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; };
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; }; E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E286D0C623B8D51100570D1E /* Observation+Combine.swift */; };
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B22449ED2000E7A40A /* Reset.swift */; };
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B22449ED2000E7A40A /* Reset.swift */; };
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B22449ED2000E7A40A /* Reset.swift */; };
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B22449ED2000E7A40A /* Reset.swift */; };
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
E3EB3E33216505920033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; }; E3EB3E33216505920033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; }; E3EB3E35216507AE0033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
E3EB3E36216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; }; E3EB3E36216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
@ -69,6 +77,8 @@
DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Defaults-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Defaults-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
DD75028D1C690C7A006590AF /* Defaults-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Defaults-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DD75028D1C690C7A006590AF /* Defaults-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Defaults-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
E286D0C623B8D51100570D1E /* Observation+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Observation+Combine.swift"; sourceTree = "<group>"; usesTabs = 1; }; E286D0C623B8D51100570D1E /* Observation+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Observation+Combine.swift"; sourceTree = "<group>"; usesTabs = 1; };
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; };
E3EB3E32216505920033B089 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; }; E3EB3E32216505920033B089 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
E3EB3E34216507AE0033B089 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observation.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 */ /* End PBXFileReference section */
@ -201,6 +211,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8933C7841EB5B820000D00A4 /* Defaults.swift */, 8933C7841EB5B820000D00A4 /* Defaults.swift */,
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
E339B3B22449ED2000E7A40A /* Reset.swift */,
E3EB3E34216507AE0033B089 /* Observation.swift */, E3EB3E34216507AE0033B089 /* Observation.swift */,
E286D0C623B8D51100570D1E /* Observation+Combine.swift */, E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
E3EB3E32216505920033B089 /* util.swift */, E3EB3E32216505920033B089 /* util.swift */,
@ -491,8 +503,10 @@
files = ( files = (
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */, E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */, 8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */, E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
E3EB3E33216505920033B089 /* util.swift in Sources */, E3EB3E33216505920033B089 /* util.swift in Sources */,
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -510,8 +524,10 @@
files = ( files = (
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */, E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
E3EB3E3A216507C40033B089 /* util.swift in Sources */, E3EB3E3A216507C40033B089 /* util.swift in Sources */,
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
E3EB3E37216507B50033B089 /* Observation.swift in Sources */, E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */, 8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -521,8 +537,10 @@
files = ( files = (
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */, E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
E3EB3E3B216507C40033B089 /* util.swift in Sources */, E3EB3E3B216507C40033B089 /* util.swift in Sources */,
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
E3EB3E38216507B60033B089 /* Observation.swift in Sources */, E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */, 8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -532,8 +550,10 @@
files = ( files = (
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */, E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
E3EB3E39216507C30033B089 /* util.swift in Sources */, E3EB3E39216507C30033B089 /* util.swift in Sources */,
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
E3EB3E36216507B50033B089 /* Observation.swift in Sources */, E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */, 8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -26,20 +26,8 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
shouldUseLaunchSchemeArgsEnv = "YES"> codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DD7502791C68FCFC006590AF"
BuildableName = "Defaults-macOS Tests.xctest"
BlueprintName = "Defaults-macOS Tests"
ReferencedContainer = "container:Defaults.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -49,8 +37,20 @@
ReferencedContainer = "container:Defaults.xcodeproj"> ReferencedContainer = "container:Defaults.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> <TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DD7502791C68FCFC006590AF"
BuildableName = "Defaults-macOS Tests.xctest"
BlueprintName = "Defaults-macOS Tests"
ReferencedContainer = "container:Defaults.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -71,8 +71,6 @@
ReferencedContainer = "container:Defaults.xcodeproj"> ReferencedContainer = "container:Defaults.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@ -1,6 +1,18 @@
// MIT License © Sindre Sorhus // MIT License © Sindre Sorhus
import Foundation import Foundation
public protocol _DefaultsBaseKey: Defaults.Keys {
var name: String { get }
var suite: UserDefaults { get }
}
extension _DefaultsBaseKey {
/// Reset the item back to its default value.
public func reset() {
suite.removeObject(forKey: name)
}
}
public enum Defaults { public enum Defaults {
public class Keys { public class Keys {
public typealias Key = Defaults.Key public typealias Key = Defaults.Key
@ -14,7 +26,7 @@ public enum Defaults {
fileprivate init() {} fileprivate init() {}
} }
public final class Key<Value: Codable>: Keys { public final class Key<Value: Codable>: Keys, _DefaultsBaseKey {
public let name: String public let name: String
public let defaultValue: Value public let defaultValue: Value
public let suite: UserDefaults public let suite: UserDefaults
@ -41,7 +53,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, *) @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>: Keys, _DefaultsBaseKey {
public let name: String public let name: String
public let defaultValue: Value public let defaultValue: Value
public let suite: UserDefaults public let suite: UserDefaults
@ -68,7 +80,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, *) @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>: Keys, _DefaultsBaseKey {
public let name: String public let name: String
public let suite: UserDefaults public let suite: UserDefaults
@ -104,108 +116,11 @@ public enum Defaults {
key.suite[key] = newValue key.suite[key] = newValue
} }
} }
}
extension Defaults {
/** /**
Reset the given keys back to their default values. Remove all entries from the given `UserDefaults` suite.
- 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<Value: Codable>(_ keys: Key<Value>..., suite: UserDefaults = .standard) {
reset(keys, suite: suite)
}
/**
Reset the given keys back to their default values.
- Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
*/
@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 static func reset<Value: NSSecureCoding>(_ keys: NSSecureCodingKey<Value>..., suite: UserDefaults = .standard) {
reset(keys, suite: suite)
}
/**
Reset the given array of keys back to their default values.
- 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<Value: Codable>(_ keys: [Key<Value>], suite: UserDefaults = .standard) {
for key in keys {
key.suite[key] = key.defaultValue
}
}
/**
Reset the given array of keys back to their default values.
- Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
*/
@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 static func reset<Value: NSSecureCoding>(_ keys: [NSSecureCodingKey<Value>], suite: UserDefaults = .standard) {
for key in keys {
key.suite[key] = key.defaultValue
}
}
/**
Reset the given optional keys back to `nil`.
- Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
*/
@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 static func reset<Value: NSSecureCoding>(_ keys: NSSecureCodingOptionalKey<Value>..., suite: UserDefaults = .standard) {
reset(keys, suite: suite)
}
/**
Reset the given array of optional keys back to `nil`.
- Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
*/
@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 static func reset<Value: NSSecureCoding>(_ keys: [NSSecureCodingOptionalKey<Value>], suite: UserDefaults = .standard) {
for key in keys {
key.suite[key] = nil
}
}
/**
Remove all entries from the `UserDefaults` suite.
*/ */
public static func removeAll(suite: UserDefaults = .standard) { public static func removeAll(suite: UserDefaults = .standard) {
for key in suite.dictionaryRepresentation().keys { for key in suite.dictionaryRepresentation().keys {
@ -226,136 +141,3 @@ extension Defaults.NSSecureCodingKey where Value: _DefaultsOptionalType {
self.init(key, default: nil, suite: suite) self.init(key, default: nil, suite: suite)
} }
} }
extension UserDefaults {
private func _get<Value: Codable>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let text = string(forKey: key),
let data = "[\(text)]".data(using: .utf8)
else {
return nil
}
do {
return (try JSONDecoder().decode([Value].self, from: data)).first
} catch {
print(error)
}
return nil
}
@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, *)
private func _get<Value: NSSecureCoding>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let data = data(forKey: key)
else {
return nil
}
do {
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
} catch {
print(error)
}
return nil
}
fileprivate func _encode<Value: Codable>(_ value: Value) -> String? {
do {
// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
let data = try JSONEncoder().encode([value])
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
} catch {
print(error)
return nil
}
}
private func _set<Value: Codable>(_ key: String, to value: Value) {
if (value as? _DefaultsOptionalType)?.isNil == true {
removeObject(forKey: key)
return
}
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(_encode(value), forKey: key)
}
@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, *)
private func _set<Value: NSSecureCoding>(_ key: String, to value: Value) {
// TODO: Handle nil here too.
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true), forKey: key)
}
public subscript<Value: Codable>(key: Defaults.Key<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@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 subscript<Value: NSSecureCoding>(key: Defaults.NSSecureCodingKey<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@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 subscript<Value: NSSecureCoding>(key: Defaults.NSSecureCodingOptionalKey<Value>) -> Value? {
get { _get(key.name) }
set {
guard let value = newValue else {
set(nil, forKey: key.name)
return
}
_set(key.name, to: value)
}
}
fileprivate static func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
switch type {
case
is Bool.Type,
is Bool?.Type, // swiftlint:disable:this discouraged_optional_boolean
is String.Type,
is String?.Type,
is Int.Type,
is Int?.Type,
is Double.Type,
is Double?.Type,
is Float.Type,
is Float?.Type,
is Date.Type,
is Date?.Type,
is Data.Type,
is Data?.Type:
return true
default:
return false
}
}
}

View File

@ -155,7 +155,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, *) @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<Value: NSSecureCoding>( public static func publisher<Value: NSSecureCoding>(
keys: Defaults.NSSecureCodingKey<Value>..., keys: NSSecureCodingKey<Value>...,
options: ObservationOptions = [.initial] options: ObservationOptions = [.initial]
) -> AnyPublisher<Void, Never> { ) -> AnyPublisher<Void, Never> {
let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher() let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
@ -177,7 +177,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, *) @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<Value: NSSecureCoding>( public static func publisher<Value: NSSecureCoding>(
keys: Defaults.NSSecureCodingOptionalKey<Value>..., keys: NSSecureCodingOptionalKey<Value>...,
options: ObservationOptions = [.initial] options: ObservationOptions = [.initial]
) -> AnyPublisher<Void, Never> { ) -> AnyPublisher<Void, Never> {
let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher() let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()

View File

@ -226,7 +226,7 @@ extension Defaults {
) -> Observation { ) -> Observation {
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
handler( handler(
KeyChange<Value>(change: change, defaultValue: key.defaultValue) KeyChange(change: change, defaultValue: key.defaultValue)
) )
} }
observation.start(options: options) observation.start(options: options)
@ -244,7 +244,7 @@ extension Defaults {
) -> Observation { ) -> Observation {
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
handler( handler(
NSSecureCodingKeyChange<Value>(change: change, defaultValue: key.defaultValue) NSSecureCodingKeyChange(change: change, defaultValue: key.defaultValue)
) )
} }
observation.start(options: options) observation.start(options: options)
@ -262,7 +262,7 @@ extension Defaults {
) -> Observation { ) -> Observation {
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
handler( handler(
NSSecureCodingOptionalKeyChange<Value>(change: change) NSSecureCodingOptionalKeyChange(change: change)
) )
} }
observation.start(options: options) observation.start(options: options)

1094
Sources/Defaults/Reset.swift Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,134 @@
import Foundation
extension UserDefaults {
private func _get<Value: Codable>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let text = string(forKey: key),
let data = "[\(text)]".data(using: .utf8)
else {
return nil
}
do {
return (try JSONDecoder().decode([Value].self, from: data)).first
} catch {
print(error)
}
return nil
}
@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, *)
private func _get<Value: NSSecureCoding>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let data = data(forKey: key)
else {
return nil
}
do {
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
} catch {
print(error)
}
return nil
}
func _encode<Value: Codable>(_ value: Value) -> String? {
do {
// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
let data = try JSONEncoder().encode([value])
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
} catch {
print(error)
return nil
}
}
private func _set<Value: Codable>(_ key: String, to value: Value) {
if (value as? _DefaultsOptionalType)?.isNil == true {
removeObject(forKey: key)
return
}
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(_encode(value), forKey: key)
}
@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, *)
private func _set<Value: NSSecureCoding>(_ key: String, to value: Value) {
// TODO: Handle nil here too.
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true), forKey: key)
}
public subscript<Value: Codable>(key: Defaults.Key<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@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 subscript<Value: NSSecureCoding>(key: Defaults.NSSecureCodingKey<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@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 subscript<Value: NSSecureCoding>(key: Defaults.NSSecureCodingOptionalKey<Value>) -> Value? {
get { _get(key.name) }
set {
guard let value = newValue else {
set(nil, forKey: key.name)
return
}
_set(key.name, to: value)
}
}
static func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
switch type {
case
is Bool.Type,
is Bool?.Type, // swiftlint:disable:this discouraged_optional_boolean
is String.Type,
is String?.Type,
is Int.Type,
is Int?.Type,
is Double.Type,
is Double?.Type,
is Float.Type,
is Float?.Type,
is Date.Type,
is Date?.Type,
is Data.Type,
is Data?.Type:
return true
default:
return false
}
}
}

View File

@ -509,46 +509,46 @@ final class DefaultsTests: XCTestCase {
} }
func testResetKey() { func testResetKey() {
let defaultString1 = "foo1" let defaultFixture1 = "foo1"
let defaultString2 = "foo2" let defaultFixture2 = 0
let defaultString3 = "foo3" let defaultFixture3 = "foo3"
let newString1 = "bar1" let newFixture1 = "bar1"
let newString2 = "bar2" let newFixture2 = 1
let newString3 = "bar3" let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("key1", default: defaultString1) let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
let key2 = Defaults.Key<String>("key2", default: defaultString2) let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
Defaults[key1] = newString1 Defaults[key1] = newFixture1
Defaults[key2] = newString2 Defaults[key2] = newFixture2
Defaults.reset(key1) Defaults.reset(key1)
XCTAssertEqual(Defaults[key1], defaultString1) XCTAssertEqual(Defaults[key1], defaultFixture1)
XCTAssertEqual(Defaults[key2], newString2) XCTAssertEqual(Defaults[key2], newFixture2)
if #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, *) { if #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, *) {
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultString3)) let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultFixture3))
Defaults[key3] = ExamplePersistentHistory(value: newString3) Defaults[key3] = ExamplePersistentHistory(value: newFixture3)
Defaults.reset(key3) Defaults.reset(key3)
XCTAssertEqual(Defaults[key3].value, defaultString3) XCTAssertEqual(Defaults[key3].value, defaultFixture3)
} }
} }
func testResetKeyArray() { func testResetMultipleKeys() {
let defaultString1 = "foo1" let defaultFxiture1 = "foo1"
let defaultString2 = "foo2" let defaultFixture2 = 0
let defaultString3 = "foo3" let defaultFixture3 = "foo3"
let newString1 = "bar1" let newFixture1 = "bar1"
let newString2 = "bar2" let newFixture2 = 1
let newString3 = "bar3" let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("akey1", default: defaultString1) let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
let key2 = Defaults.Key<String>("akey2", default: defaultString2) let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
let key3 = Defaults.Key<String>("akey3", default: defaultString3) let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
Defaults[key1] = newString1 Defaults[key1] = newFixture1
Defaults[key2] = newString2 Defaults[key2] = newFixture2
Defaults[key3] = newString3 Defaults[key3] = newFixture3
Defaults.reset(key1, key2) Defaults.reset(key1, key2)
XCTAssertEqual(Defaults[key1], defaultString1) XCTAssertEqual(Defaults[key1], defaultFxiture1)
XCTAssertEqual(Defaults[key2], defaultString2) XCTAssertEqual(Defaults[key2], defaultFixture2)
XCTAssertEqual(Defaults[key3], newString3) XCTAssertEqual(Defaults[key3], newFixture3)
} }
func testResetOptionalKey() { func testResetOptionalKey() {
@ -571,20 +571,20 @@ final class DefaultsTests: XCTestCase {
} }
} }
func testResetOptionalKeyArray() { func testResetMultipleOptionalKeys() {
let newString1 = "bar1" let newFixture1 = "bar1"
let newString2 = "bar2" let newFixture2 = 1
let newString3 = "bar3" let newFixture3 = "bar3"
let key1 = Defaults.Key<String?>("aoptionalKey1") let key1 = Defaults.Key<String?>("aoptionalKey1")
let key2 = Defaults.Key<String?>("aoptionalKey2") let key2 = Defaults.Key<Int?>("aoptionalKey2")
let key3 = Defaults.Key<String?>("aoptionalKey3") let key3 = Defaults.Key<String?>("aoptionalKey3")
Defaults[key1] = newString1 Defaults[key1] = newFixture1
Defaults[key2] = newString2 Defaults[key2] = newFixture2
Defaults[key3] = newString3 Defaults[key3] = newFixture3
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], newFixture3)
} }
func testObserveWithLifetimeTie() { func testObserveWithLifetimeTie() {

View File

@ -318,22 +318,16 @@ Type: `class`
Create a NSSecureCoding key with an optional value. Create a NSSecureCoding key with an optional value.
#### `Defaults.reset` #### `Defaults.reset(key, …)`
```swift
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.NSSecureCodingKey<T>..., suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: [Defaults.NSSecureCodingKey<T>], suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: Defaults.NSSecureCodingOptionalKey<T>..., suite: UserDefaults = .standard)
Defaults.reset<T: Codable>(_ keys: [Defaults.NSSecureCodingOptionalKey<T>], suite: UserDefaults = .standard)
```
Type: `func` Type: `func`
Reset the given keys back to their default values. Reset the given keys back to their default values.
You can specify up to 10 keys. If you need to specify more, call this method multiple times.
You can also specify string keys, which can be useful if you need to store some keys in a collection, as it's not possible to store `Defaults.Key` in a collection because it's generic.
#### `Defaults.observe` #### `Defaults.observe`
```swift ```swift
@ -444,7 +438,7 @@ Defaults.removeAll(suite: UserDefaults = .standard)
Type: `func` Type: `func`
Remove all entries from the `UserDefaults` suite. Remove all entries from the given `UserDefaults` suite.
### `Defaults.Observation` ### `Defaults.Observation`