Fix `Defaults.reset()`
This commit is contained in:
parent
77c05abe10
commit
15c096d7fd
|
@ -21,6 +21,14 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
E3EB3E35216507AE0033B089 /* 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; };
|
||||
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; };
|
||||
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; };
|
||||
E3EB3E34216507AE0033B089 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observation.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -201,6 +211,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
8933C7841EB5B820000D00A4 /* Defaults.swift */,
|
||||
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
|
||||
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
||||
E3EB3E34216507AE0033B089 /* Observation.swift */,
|
||||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
||||
E3EB3E32216505920033B089 /* util.swift */,
|
||||
|
@ -491,8 +503,10 @@
|
|||
files = (
|
||||
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
|
||||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
||||
E3EB3E33216505920033B089 /* util.swift in Sources */,
|
||||
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -510,8 +524,10 @@
|
|||
files = (
|
||||
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E3EB3E3A216507C40033B089 /* util.swift in Sources */,
|
||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
||||
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -521,8 +537,10 @@
|
|||
files = (
|
||||
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E3EB3E3B216507C40033B089 /* util.swift in Sources */,
|
||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
||||
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -532,8 +550,10 @@
|
|||
files = (
|
||||
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
||||
E3EB3E39216507C30033B089 /* util.swift in Sources */,
|
||||
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
|
||||
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -26,20 +26,8 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "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>
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
@ -49,8 +37,20 @@
|
|||
ReferencedContainer = "container:Defaults.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
<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>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
@ -71,8 +71,6 @@
|
|||
ReferencedContainer = "container:Defaults.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
// MIT License © Sindre Sorhus
|
||||
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 class Keys {
|
||||
public typealias Key = Defaults.Key
|
||||
|
@ -14,7 +26,7 @@ public enum Defaults {
|
|||
fileprivate init() {}
|
||||
}
|
||||
|
||||
public final class Key<Value: Codable>: Keys {
|
||||
public final class Key<Value: Codable>: Keys, _DefaultsBaseKey {
|
||||
public let name: String
|
||||
public let defaultValue: Value
|
||||
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, *)
|
||||
public final class NSSecureCodingKey<Value: NSSecureCoding>: Keys {
|
||||
public final class NSSecureCodingKey<Value: NSSecureCoding>: Keys, _DefaultsBaseKey {
|
||||
public let name: String
|
||||
public let defaultValue: Value
|
||||
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, *)
|
||||
public final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: Keys {
|
||||
public final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: Keys, _DefaultsBaseKey {
|
||||
public let name: String
|
||||
public let suite: UserDefaults
|
||||
|
||||
|
@ -104,108 +116,11 @@ public enum Defaults {
|
|||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
/**
|
||||
Reset the given 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) {
|
||||
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.
|
||||
Remove all entries from the given `UserDefaults` suite.
|
||||
*/
|
||||
public static func removeAll(suite: UserDefaults = .standard) {
|
||||
for key in suite.dictionaryRepresentation().keys {
|
||||
|
@ -226,136 +141,3 @@ extension Defaults.NSSecureCodingKey where Value: _DefaultsOptionalType {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,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, *)
|
||||
public static func publisher<Value: Codable>(
|
||||
keys:Key<Value>...,
|
||||
keys: Key<Value>...,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<Void, Never> {
|
||||
let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
|
||||
|
@ -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, *)
|
||||
public static func publisher<Value: NSSecureCoding>(
|
||||
keys: Defaults.NSSecureCodingKey<Value>...,
|
||||
keys: NSSecureCodingKey<Value>...,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<Void, Never> {
|
||||
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, *)
|
||||
public static func publisher<Value: NSSecureCoding>(
|
||||
keys: Defaults.NSSecureCodingOptionalKey<Value>...,
|
||||
keys: NSSecureCodingOptionalKey<Value>...,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<Void, Never> {
|
||||
let initial = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
|
||||
|
|
|
@ -226,7 +226,7 @@ extension Defaults {
|
|||
) -> Observation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
KeyChange<Value>(change: change, defaultValue: key.defaultValue)
|
||||
KeyChange(change: change, defaultValue: key.defaultValue)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
|
@ -244,7 +244,7 @@ extension Defaults {
|
|||
) -> Observation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingKeyChange<Value>(change: change, defaultValue: key.defaultValue)
|
||||
NSSecureCodingKeyChange(change: change, defaultValue: key.defaultValue)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
|
@ -262,7 +262,7 @@ extension Defaults {
|
|||
) -> Observation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingOptionalKeyChange<Value>(change: change)
|
||||
NSSecureCodingOptionalKeyChange(change: change)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -509,46 +509,46 @@ final class DefaultsTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testResetKey() {
|
||||
let defaultString1 = "foo1"
|
||||
let defaultString2 = "foo2"
|
||||
let defaultString3 = "foo3"
|
||||
let newString1 = "bar1"
|
||||
let newString2 = "bar2"
|
||||
let newString3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("key1", default: defaultString1)
|
||||
let key2 = Defaults.Key<String>("key2", default: defaultString2)
|
||||
Defaults[key1] = newString1
|
||||
Defaults[key2] = newString2
|
||||
let defaultFixture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let defaultFixture3 = "foo3"
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
|
||||
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults.reset(key1)
|
||||
XCTAssertEqual(Defaults[key1], defaultString1)
|
||||
XCTAssertEqual(Defaults[key2], newString2)
|
||||
XCTAssertEqual(Defaults[key1], defaultFixture1)
|
||||
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, *) {
|
||||
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultString3))
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newString3)
|
||||
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultFixture3))
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newFixture3)
|
||||
Defaults.reset(key3)
|
||||
|
||||
XCTAssertEqual(Defaults[key3].value, defaultString3)
|
||||
XCTAssertEqual(Defaults[key3].value, defaultFixture3)
|
||||
}
|
||||
}
|
||||
|
||||
func testResetKeyArray() {
|
||||
let defaultString1 = "foo1"
|
||||
let defaultString2 = "foo2"
|
||||
let defaultString3 = "foo3"
|
||||
let newString1 = "bar1"
|
||||
let newString2 = "bar2"
|
||||
let newString3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("akey1", default: defaultString1)
|
||||
let key2 = Defaults.Key<String>("akey2", default: defaultString2)
|
||||
let key3 = Defaults.Key<String>("akey3", default: defaultString3)
|
||||
Defaults[key1] = newString1
|
||||
Defaults[key2] = newString2
|
||||
Defaults[key3] = newString3
|
||||
func testResetMultipleKeys() {
|
||||
let defaultFxiture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let defaultFixture3 = "foo3"
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
|
||||
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
|
||||
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertEqual(Defaults[key1], defaultString1)
|
||||
XCTAssertEqual(Defaults[key2], defaultString2)
|
||||
XCTAssertEqual(Defaults[key3], newString3)
|
||||
XCTAssertEqual(Defaults[key1], defaultFxiture1)
|
||||
XCTAssertEqual(Defaults[key2], defaultFixture2)
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testResetOptionalKey() {
|
||||
|
@ -571,20 +571,20 @@ final class DefaultsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testResetOptionalKeyArray() {
|
||||
let newString1 = "bar1"
|
||||
let newString2 = "bar2"
|
||||
let newString3 = "bar3"
|
||||
func testResetMultipleOptionalKeys() {
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String?>("aoptionalKey1")
|
||||
let key2 = Defaults.Key<String?>("aoptionalKey2")
|
||||
let key2 = Defaults.Key<Int?>("aoptionalKey2")
|
||||
let key3 = Defaults.Key<String?>("aoptionalKey3")
|
||||
Defaults[key1] = newString1
|
||||
Defaults[key2] = newString2
|
||||
Defaults[key3] = newString3
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertEqual(Defaults[key1], nil)
|
||||
XCTAssertEqual(Defaults[key2], nil)
|
||||
XCTAssertEqual(Defaults[key3], newString3)
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testObserveWithLifetimeTie() {
|
||||
|
|
18
readme.md
18
readme.md
|
@ -318,22 +318,16 @@ Type: `class`
|
|||
|
||||
Create a NSSecureCoding key with an optional value.
|
||||
|
||||
#### `Defaults.reset`
|
||||
|
||||
```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)
|
||||
```
|
||||
#### `Defaults.reset(key, …)`
|
||||
|
||||
Type: `func`
|
||||
|
||||
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`
|
||||
|
||||
```swift
|
||||
|
@ -444,7 +438,7 @@ Defaults.removeAll(suite: UserDefaults = .standard)
|
|||
|
||||
Type: `func`
|
||||
|
||||
Remove all entries from the `UserDefaults` suite.
|
||||
Remove all entries from the given `UserDefaults` suite.
|
||||
|
||||
### `Defaults.Observation`
|
||||
|
||||
|
|
Loading…
Reference in New Issue