Add support for NSSecureCoding (#27)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
54f970b9d7
commit
89d2d4d353
|
@ -4,8 +4,15 @@ import Foundation
|
|||
public final class Defaults {
|
||||
public class Keys {
|
||||
public typealias Key = Defaults.Key
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public typealias NSSecureCodingKey = Defaults.NSSecureCodingKey
|
||||
|
||||
public typealias OptionalKey = Defaults.OptionalKey
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public typealias NSSecureCodingOptionalKey = Defaults.NSSecureCodingOptionalKey
|
||||
|
||||
fileprivate init() {}
|
||||
}
|
||||
|
||||
|
@ -31,6 +38,29 @@ public final class Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public final class NSSecureCodingKey<T: NSSecureCoding>: Keys {
|
||||
public let name: String
|
||||
public let defaultValue: T
|
||||
public let suite: UserDefaults
|
||||
|
||||
/// Create a defaults key.
|
||||
public init(_ key: String, default defaultValue: T, suite: UserDefaults = .standard) {
|
||||
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 = try? NSKeyedArchiver.archivedData(withRootObject: defaultValue, requiringSecureCoding: true) {
|
||||
suite.register(defaults: [key: value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class OptionalKey<T: Codable>: Keys {
|
||||
public let name: String
|
||||
public let suite: UserDefaults
|
||||
|
@ -42,6 +72,18 @@ public final class Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public final class NSSecureCodingOptionalKey<T: NSSecureCoding>: Keys {
|
||||
public let name: String
|
||||
public let suite: UserDefaults
|
||||
|
||||
/// Create an optional defaults key.
|
||||
public init(_ key: String, suite: UserDefaults = .standard) {
|
||||
self.name = key
|
||||
self.suite = suite
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate init() {}
|
||||
|
||||
/// Access a defaults value using a `Defaults.Key`.
|
||||
|
@ -52,6 +94,15 @@ public final class Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.NSSecureCodingKey`.
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static subscript<T: NSSecureCoding>(key: NSSecureCodingKey<T>) -> T {
|
||||
get { key.suite[key] }
|
||||
set {
|
||||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.OptionalKey`.
|
||||
public static subscript<T: Codable>(key: OptionalKey<T>) -> T? {
|
||||
get { key.suite[key] }
|
||||
|
@ -59,6 +110,15 @@ public final class Defaults {
|
|||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.OptionalKey`.
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static subscript<T: NSSecureCoding>(key: NSSecureCodingOptionalKey<T>) -> T? {
|
||||
get { key.suite[key] }
|
||||
set {
|
||||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given keys back to their default values.
|
||||
|
@ -83,6 +143,17 @@ public final class Defaults {
|
|||
public static func reset<T: Codable>(_ keys: Key<T>..., 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(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func reset<T: NSSecureCoding>(_ keys: NSSecureCodingKey<T>..., suite: UserDefaults = .standard) {
|
||||
reset(keys, suite: suite)
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given array of keys back to their default values.
|
||||
|
@ -109,6 +180,19 @@ public final class Defaults {
|
|||
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(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func reset<T: NSSecureCoding>(_ keys: [NSSecureCodingKey<T>], suite: UserDefaults = .standard) {
|
||||
for key in keys {
|
||||
key.suite[key] = key.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given optional keys back to `nil`.
|
||||
|
@ -132,6 +216,18 @@ public final class Defaults {
|
|||
public static func reset<T: Codable>(_ keys: OptionalKey<T>..., suite: UserDefaults = .standard) {
|
||||
reset(keys, suite: suite)
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given optional keys back to `nil`.
|
||||
|
||||
- Parameter keys: Keys to reset.
|
||||
- Parameter suite: `UserDefaults` suite.
|
||||
```
|
||||
*/
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func reset<T: NSSecureCoding>(_ keys: NSSecureCodingOptionalKey<T>..., suite: UserDefaults = .standard) {
|
||||
reset(keys, suite: suite)
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given array of optional keys back to `nil`.
|
||||
|
@ -158,6 +254,19 @@ public final class Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the given array of optional keys back to `nil`.
|
||||
|
||||
- Parameter keys: Keys to reset.
|
||||
- Parameter suite: `UserDefaults` suite.
|
||||
*/
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func reset<T: NSSecureCoding>(_ keys: [NSSecureCodingOptionalKey<T>], suite: UserDefaults = .standard) {
|
||||
for key in keys {
|
||||
key.suite[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all entries from the `UserDefaults` suite.
|
||||
*/
|
||||
|
@ -190,6 +299,27 @@ extension UserDefaults {
|
|||
return nil
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private func _get<T: NSSecureCoding>(_ key: String) -> T? {
|
||||
if UserDefaults.isNativelySupportedType(T.self) {
|
||||
return object(forKey: key) as? T
|
||||
}
|
||||
|
||||
guard
|
||||
let data = data(forKey: key)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func _encode<T: Codable>(_ value: T) -> String? {
|
||||
do {
|
||||
// Some codable values like URL and enum are encoded as a top-level
|
||||
|
@ -212,6 +342,16 @@ extension UserDefaults {
|
|||
set(_encode(value), forKey: key)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private func _set<T: NSSecureCoding>(_ key: String, to value: T) {
|
||||
if UserDefaults.isNativelySupportedType(T.self) {
|
||||
set(value, forKey: key)
|
||||
return
|
||||
}
|
||||
|
||||
set(try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true), forKey: key)
|
||||
}
|
||||
|
||||
public subscript<T: Codable>(key: Defaults.Key<T>) -> T {
|
||||
get { _get(key.name) ?? key.defaultValue }
|
||||
set {
|
||||
|
@ -219,6 +359,14 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public subscript<T: NSSecureCoding>(key: Defaults.NSSecureCodingKey<T>) -> T {
|
||||
get { _get(key.name) ?? key.defaultValue }
|
||||
set {
|
||||
_set(key.name, to: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public subscript<T: Codable>(key: Defaults.OptionalKey<T>) -> T? {
|
||||
get { _get(key.name) }
|
||||
set {
|
||||
|
@ -231,6 +379,19 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public subscript<T: NSSecureCoding>(key: Defaults.NSSecureCodingOptionalKey<T>) -> T? {
|
||||
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,
|
||||
|
|
|
@ -43,6 +43,27 @@ extension Defaults {
|
|||
return [T].init(jsonString: "\([value])")?.first
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private static func deserialize<T: NSSecureCoding>(_ value: Any?, to type: T.Type) -> T? {
|
||||
guard
|
||||
let value = value,
|
||||
!(value is NSNull)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This handles the case where the value was a plist value using `isNativelySupportedType`
|
||||
if let value = value as? T {
|
||||
return value
|
||||
}
|
||||
|
||||
guard let dataValue = value as? Data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(dataValue) as? T
|
||||
}
|
||||
|
||||
fileprivate final class BaseChange {
|
||||
fileprivate let kind: NSKeyValueChange
|
||||
fileprivate let indexes: IndexSet?
|
||||
|
@ -75,6 +96,23 @@ extension Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public struct NSSecureCodingKeyChange<T: NSSecureCoding> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
public let isPrior: Bool
|
||||
public let newValue: T
|
||||
public let oldValue: T
|
||||
|
||||
fileprivate init(change: BaseChange, defaultValue: T) {
|
||||
self.kind = change.kind
|
||||
self.indexes = change.indexes
|
||||
self.isPrior = change.isPrior
|
||||
self.oldValue = deserialize(change.oldValue, to: T.self) ?? defaultValue
|
||||
self.newValue = deserialize(change.newValue, to: T.self) ?? defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
public struct OptionalKeyChange<T: Codable> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
|
@ -91,6 +129,23 @@ extension Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public struct NSSecureCodingOptionalKeyChange<T: NSSecureCoding> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
public let isPrior: Bool
|
||||
public let newValue: T?
|
||||
public let oldValue: T?
|
||||
|
||||
fileprivate init(change: BaseChange) {
|
||||
self.kind = change.kind
|
||||
self.indexes = change.indexes
|
||||
self.isPrior = change.isPrior
|
||||
self.oldValue = deserialize(change.oldValue, to: T.self)
|
||||
self.newValue = deserialize(change.newValue, to: T.self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class UserDefaultsKeyObservation: NSObject, DefaultsObservation {
|
||||
fileprivate typealias Callback = (BaseChange) -> Void
|
||||
|
||||
|
@ -182,6 +237,24 @@ extension Defaults {
|
|||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe a defaults key.
|
||||
*/
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func observe<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingKey<T>,
|
||||
options: NSKeyValueObservingOptions = [.initial, .old, .new],
|
||||
handler: @escaping (NSSecureCodingKeyChange<T>) -> Void
|
||||
) -> DefaultsObservation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingKeyChange<T>(change: change, defaultValue: key.defaultValue)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe an optional defaults key.
|
||||
|
||||
|
@ -209,4 +282,22 @@ extension Defaults {
|
|||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe an optional defaults key.
|
||||
*/
|
||||
@available(iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func observe<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingOptionalKey<T>,
|
||||
options: NSKeyValueObservingOptions = [.initial, .old, .new],
|
||||
handler: @escaping (NSSecureCodingOptionalKeyChange<T>) -> Void
|
||||
) -> DefaultsObservation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingOptionalKeyChange<T>(change: change)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
import CoreData
|
||||
|
||||
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
||||
let fixtureURL2 = URL(string: "https://example.com")!
|
||||
|
@ -13,12 +14,42 @@ enum FixtureEnum: String, Codable {
|
|||
|
||||
let fixtureDate = Date()
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
final class ExamplePersistentHistory: NSPersistentHistoryToken {
|
||||
|
||||
let value: String
|
||||
|
||||
init(value: String) {
|
||||
self.value = value
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
self.value = coder.decodeObject(forKey: "value") as! String
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func encode(with coder: NSCoder) {
|
||||
coder.encode(value, forKey: "value")
|
||||
}
|
||||
|
||||
override class var supportsSecureCoding: Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let key = Key<Bool>("key", default: false)
|
||||
static let url = Key<URL>("url", default: fixtureURL)
|
||||
static let `enum` = Key<FixtureEnum>("enum", default: .oneHour)
|
||||
static let data = Key<Data>("data", default: Data([]))
|
||||
static let date = Key<Date>("date", default: fixtureDate)
|
||||
|
||||
// NSSecureCoding
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
static let persistentHistoryValue = ExamplePersistentHistory(value: "ExampleToken")
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
static let persistentHistory = NSSecureCodingKey<ExamplePersistentHistory>("persistentHistory", default: persistentHistoryValue)
|
||||
}
|
||||
|
||||
final class DefaultsTests: XCTestCase {
|
||||
|
@ -75,6 +106,14 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTAssertTrue(Defaults[.key])
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
func testNSSecureCodingKeys() {
|
||||
XCTAssertEqual(Defaults.Keys.persistentHistoryValue.value, Defaults[.persistentHistory].value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[.persistentHistory] = newPersistentHistory
|
||||
XCTAssertEqual(newPersistentHistory.value, Defaults[.persistentHistory].value)
|
||||
}
|
||||
|
||||
func testUrlType() {
|
||||
XCTAssertEqual(Defaults[.url], fixtureURL)
|
||||
|
||||
|
@ -143,6 +182,24 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
func testObserveNSSecureCodingKey() {
|
||||
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: DefaultsObservation!
|
||||
observation = Defaults.observe(key, options: [.old, .new]) { change in
|
||||
XCTAssertEqual(change.oldValue.value, "TestValue")
|
||||
XCTAssertEqual(change.newValue.value, "NewTestValue")
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.OptionalKey<Bool>("observeOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
@ -160,6 +217,24 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
|
||||
func testObserveNSSecureCodingOptionalKey() {
|
||||
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: DefaultsObservation!
|
||||
observation = Defaults.observe(key, options: [.old, .new]) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue?.value, "NewOptionalValue")
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewOptionalValue")
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKeyURL() {
|
||||
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
||||
let fixtureURL2 = URL(string: "https://example.com")!
|
||||
|
@ -199,8 +274,10 @@ 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
|
||||
|
@ -208,6 +285,14 @@ final class DefaultsTests: XCTestCase {
|
|||
Defaults.reset(key1)
|
||||
XCTAssertEqual(Defaults[key1], defaultString1)
|
||||
XCTAssertEqual(Defaults[key2], newString2)
|
||||
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) {
|
||||
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultString3))
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newString3)
|
||||
Defaults.reset(key3)
|
||||
|
||||
XCTAssertEqual(Defaults[key3].value, defaultString3)
|
||||
}
|
||||
}
|
||||
|
||||
func testResetKeyArray() {
|
||||
|
@ -232,6 +317,7 @@ final class DefaultsTests: XCTestCase {
|
|||
func testResetOptionalKey() {
|
||||
let newString1 = "bar1"
|
||||
let newString2 = "bar2"
|
||||
let newString3 = "bar3"
|
||||
let key1 = Defaults.OptionalKey<String>("optionalKey1")
|
||||
let key2 = Defaults.OptionalKey<String>("optionalKey2")
|
||||
Defaults[key1] = newString1
|
||||
|
@ -239,6 +325,13 @@ final class DefaultsTests: XCTestCase {
|
|||
Defaults.reset(key1)
|
||||
XCTAssertEqual(Defaults[key1], nil)
|
||||
XCTAssertEqual(Defaults[key2], newString2)
|
||||
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) {
|
||||
let key3 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("optionalKey3")
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newString3)
|
||||
Defaults.reset(key3)
|
||||
XCTAssertEqual(Defaults[key3], nil)
|
||||
}
|
||||
}
|
||||
|
||||
func testResetOptionalKeyArray() {
|
||||
|
|
66
readme.md
66
readme.md
|
@ -9,6 +9,7 @@ This package is used in production by apps like [Gifski](https://github.com/sind
|
|||
|
||||
- **Strongly typed:** You declare the type and default value upfront.
|
||||
- **Codable support:** You can store any [Codable](https://developer.apple.com/documentation/swift/codable) value, like an enum.
|
||||
- **NSSecureCoding support:** You can store any [NSSecureCoding](https://developer.apple.com/documentation/foundation/nssecurecoding) value.
|
||||
- **Debuggable:** The data is stored as JSON-serialized values.
|
||||
- **Observation:** Observe changes to keys.
|
||||
- **Lightweight:** It's only ~300 lines of code.
|
||||
|
@ -88,6 +89,28 @@ if let name = Defaults[.name] {
|
|||
|
||||
The default value is then `nil`.
|
||||
|
||||
---
|
||||
|
||||
If you have `NSSecureCoding` classes which you want to save, you can use them as follows:
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let someSecureCoding = NSSecureCodingKey<SomeNSSecureCodingClass>("someSecureCoding", default: SomeNSSecureCodingClass(string: "Default", int: 5, bool: true))
|
||||
static let someOptionalSecureCoding = NSSecureCodingOptionalKey<Double>("someOptionalSecureCoding")
|
||||
}
|
||||
|
||||
Defaults[.someSecureCoding].string
|
||||
//=> "Default"
|
||||
|
||||
Defaults[.someSecureCoding].int
|
||||
//=> 5
|
||||
|
||||
Defaults[.someSecureCoding].bool
|
||||
//=> true
|
||||
```
|
||||
|
||||
You can use those keys just like in all the other examples. The return value will be your `NSSecureCoding` class.
|
||||
|
||||
### Enum example
|
||||
|
||||
```swift
|
||||
|
@ -248,6 +271,18 @@ Create a key with a default value.
|
|||
|
||||
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
|
||||
|
||||
#### `Defaults.NSSecureCodingKey` *(alias `Defaults.Keys.NSSecureCodingKey`)*
|
||||
|
||||
```swift
|
||||
Defaults.NSSecureCodingKey<T>(_ key: String, default: T, suite: UserDefaults = .standard)
|
||||
```
|
||||
|
||||
Type: `class`
|
||||
|
||||
Create a NSSecureCoding key with a default value.
|
||||
|
||||
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`)*
|
||||
|
||||
```swift
|
||||
|
@ -258,6 +293,16 @@ Type: `class`
|
|||
|
||||
Create a key with an optional value.
|
||||
|
||||
#### `Defaults.NSSecureCodingOptionalKey` *(alias `Defaults.Keys.NSSecureCodingOptionalKey`)*
|
||||
|
||||
```swift
|
||||
Defaults.NSSecureCodingOptionalKey<T>(_ key: String, suite: UserDefaults = .standard)
|
||||
```
|
||||
|
||||
Type: `class`
|
||||
|
||||
Create a NSSecureCoding key with an optional value.
|
||||
|
||||
#### `Defaults.reset`
|
||||
|
||||
```swift
|
||||
|
@ -265,6 +310,11 @@ Defaults.reset<T: Codable>(_ keys: Defaults.Key<T>..., suite: UserDefaults = .st
|
|||
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)
|
||||
|
||||
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`
|
||||
|
@ -281,6 +331,14 @@ Defaults.observe<T: Codable>(
|
|||
) -> DefaultsObservation
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.observe<T: Codable>(
|
||||
_ key: Defaults.NSSecureCodingKey<T>,
|
||||
options: NSKeyValueObservingOptions = [.initial, .old, .new],
|
||||
handler: @escaping (NSSecureCodingKeyChange<T>) -> Void
|
||||
) -> DefaultsObservation
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.observe<T: Codable>(
|
||||
_ key: Defaults.OptionalKey<T>,
|
||||
|
@ -289,6 +347,14 @@ Defaults.observe<T: Codable>(
|
|||
) -> DefaultsObservation
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.observe<T: Codable>(
|
||||
_ key: Defaults.NSSecureCodingOptionalKey<T>,
|
||||
options: NSKeyValueObservingOptions = [.initial, .old, .new],
|
||||
handler: @escaping (NSSecureCodingOptionalKeyChange<T>) -> Void
|
||||
) -> DefaultsObservation
|
||||
```
|
||||
|
||||
Type: `func`
|
||||
|
||||
Observe changes to a key or an optional key.
|
||||
|
|
Loading…
Reference in New Issue