Defaults/Sources/Defaults/Defaults.swift

249 lines
5.3 KiB
Swift
Raw Normal View History

2018-04-12 01:19:30 +08:00
// MIT License © Sindre Sorhus
import Foundation
2018-04-12 01:19:30 +08:00
public final class Defaults {
public class Keys {
public typealias Key = Defaults.Key
public typealias OptionalKey = Defaults.OptionalKey
fileprivate init() {}
}
2018-04-12 01:19:30 +08:00
public final class Key<T: Codable>: Keys {
public let name: String
public let defaultValue: T
public let suite: UserDefaults
2018-04-12 01:19:30 +08:00
/// Create a defaults key.
public init(_ key: String, default defaultValue: T, suite: UserDefaults = .standard) {
2018-04-12 01:19:30 +08:00
self.name = key
self.defaultValue = defaultValue
self.suite = suite
super.init()
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
if UserDefaults.isNativelySupportedType(T.self) {
suite.register(defaults: [key: defaultValue])
} else if let value = suite._encode(defaultValue) {
suite.register(defaults: [key: value])
}
2018-04-12 01:19:30 +08:00
}
}
public final class OptionalKey<T: Codable>: Keys {
public let name: String
public let suite: UserDefaults
2018-04-12 01:19:30 +08:00
/// Create an optional defaults key.
public init(_ key: String, suite: UserDefaults = .standard) {
2018-04-12 01:19:30 +08:00
self.name = key
self.suite = suite
2018-04-12 01:19:30 +08:00
}
}
fileprivate init() {}
/// Access a defaults value using a `Defaults.Key`.
2019-09-11 15:56:11 +08:00
public static subscript<T: Codable>(key: Key<T>) -> T {
get { key.suite[key] }
2018-04-12 01:19:30 +08:00
set {
key.suite[key] = newValue
2018-04-12 01:19:30 +08:00
}
}
/// Access a defaults value using a `Defaults.OptionalKey`.
2019-09-11 15:56:11 +08:00
public static subscript<T: Codable>(key: OptionalKey<T>) -> T? {
get { key.suite[key] }
2018-04-12 01:19:30 +08:00
set {
key.suite[key] = newValue
2018-04-12 01:19:30 +08:00
}
}
/**
Reset the given keys back to their default values.
2018-04-12 01:19:30 +08:00
- Parameter keys: Keys to reset.
- Parameter suite: `UserDefaults` suite.
```
extension Defaults.Keys {
static let isUnicornMode = Key<Bool>("isUnicornMode", default: false)
2018-04-12 01:19:30 +08:00
}
Defaults[.isUnicornMode] = true
//=> true
Defaults.reset(.isUnicornMode)
Defaults[.isUnicornMode]
//=> false
```
*/
2019-09-11 15:56:11 +08:00
public static func reset<T: Codable>(_ keys: Key<T>..., 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
```
*/
2019-09-11 15:56:11 +08:00
public static func reset<T: Codable>(_ keys: [Key<T>], 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.
```
extension Defaults.Keys {
static let unicorn = OptionalKey<String>("unicorn")
}
Defaults[.unicorn] = "🦄"
Defaults.reset(.unicorn)
Defaults[.unicorn]
//=> nil
```
*/
2019-09-11 15:56:11 +08:00
public static func reset<T: Codable>(_ keys: OptionalKey<T>..., 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.
```
extension Defaults.Keys {
static let unicorn = OptionalKey<String>("unicorn")
}
Defaults[.unicorn] = "🦄"
Defaults.reset(.unicorn)
Defaults[.unicorn]
//=> nil
```
*/
2019-09-11 15:56:11 +08:00
public static func reset<T: Codable>(_ keys: [OptionalKey<T>], suite: UserDefaults = .standard) {
for key in keys {
key.suite[key] = nil
}
}
2018-04-12 01:19:30 +08:00
/**
Remove all entries from the `UserDefaults` suite.
*/
public static func removeAll(suite: UserDefaults = .standard) {
for key in suite.dictionaryRepresentation().keys {
suite.removeObject(forKey: key)
}
}
}
2018-04-12 01:19:30 +08:00
2018-06-29 16:49:06 +08:00
extension UserDefaults {
2018-04-12 01:19:30 +08:00
private func _get<T: Codable>(_ key: String) -> T? {
if UserDefaults.isNativelySupportedType(T.self) {
2018-04-12 01:19:30 +08:00
return object(forKey: key) as? T
}
2018-06-29 16:49:06 +08:00
guard
let text = string(forKey: key),
let data = "[\(text)]".data(using: .utf8)
else {
return nil
2018-04-12 01:19:30 +08:00
}
do {
return (try JSONDecoder().decode([T].self, from: data)).first
} catch {
print(error)
}
return nil
}
fileprivate func _encode<T: Codable>(_ value: T) -> String? {
2018-04-12 01:19:30 +08:00
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())
2018-04-12 01:19:30 +08:00
} catch {
print(error)
return nil
2018-04-12 01:19:30 +08:00
}
}
private func _set<T: Codable>(_ key: String, to value: T) {
if UserDefaults.isNativelySupportedType(T.self) {
set(value, forKey: key)
return
}
set(_encode(value), forKey: key)
}
2018-04-12 01:19:30 +08:00
public subscript<T: Codable>(key: Defaults.Key<T>) -> T {
get { _get(key.name) ?? key.defaultValue }
2018-04-12 01:19:30 +08:00
set {
_set(key.name, to: newValue)
}
}
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
get { _get(key.name) }
2018-04-12 01:19:30 +08:00
set {
guard let value = newValue else {
set(nil, forKey: key.name)
return
2018-04-12 01:19:30 +08:00
}
_set(key.name, to: value)
2018-04-12 01:19:30 +08:00
}
}
fileprivate static func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
2018-04-12 01:19:30 +08:00
switch type {
case is Bool.Type,
is String.Type,
is Int.Type,
is Double.Type,
is Float.Type,
2018-04-16 02:09:53 +08:00
is Date.Type,
is Data.Type:
2018-04-12 01:19:30 +08:00
return true
default:
return false
}
}
}