Defaults/Sources/Defaults.swift

131 lines
2.7 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 {
fileprivate init() {}
}
2018-04-12 01:19:30 +08:00
public final class Key<T: Codable>: Keys {
fileprivate let name: String
fileprivate let defaultValue: T
public init(_ key: String, default defaultValue: T) {
2018-04-12 01:19:30 +08:00
self.name = key
self.defaultValue = defaultValue
}
}
public final class OptionalKey<T: Codable>: Keys {
fileprivate let name: String
public init(_ key: String) {
2018-04-12 01:19:30 +08:00
self.name = key
}
}
public subscript<T: Codable>(key: Defaults.Key<T>) -> T {
get {
return UserDefaults.standard[key]
}
set {
UserDefaults.standard[key] = newValue
}
}
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
get {
return UserDefaults.standard[key]
}
set {
UserDefaults.standard[key] = newValue
}
}
public func clear() {
for key in UserDefaults.standard.dictionaryRepresentation().keys {
UserDefaults.standard.removeObject(forKey: key)
}
}
}
// Has to be `defaults` lowercase until Swift supports static subscripts
public let defaults = Defaults()
public extension UserDefaults {
private func _get<T: Codable>(_ key: String) -> T? {
if isNativelySupportedType(T.self) {
return object(forKey: key) as? T
}
guard let text = string(forKey: key),
let data = "[\(text)]".data(using: .utf8) else {
return nil
}
do {
return (try JSONDecoder().decode([T].self, from: data)).first
} catch {
print(error)
}
return nil
}
private func _set<T: Codable>(_ key: String, to value: T) {
if isNativelySupportedType(T.self) {
set(value, forKey: key)
return
}
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])
let string = String(data: data, encoding: .utf8)?.dropFirst().dropLast()
set(string, forKey: key)
} catch {
print(error)
}
}
public subscript<T: Codable>(key: Defaults.Key<T>) -> T {
get {
return _get(key.name) ?? key.defaultValue
}
set {
_set(key.name, to: newValue)
}
}
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
get {
return _get(key.name)
}
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
}
}
private func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
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
}
}
}