Add `Defaults.PreferNSSecureCoding` and `Defaults.PreferRawRepresentable` (#83)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
hank121314 2021-10-12 15:03:34 +08:00 committed by GitHub
parent 04f9b3f45e
commit 5f16cefebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 4 deletions

View File

@ -90,14 +90,21 @@ extension Defaults.Serializable where Self: Codable & NSSecureCoding {
public static var bridge: Defaults.CodableNSSecureCodingBridge<Self> { Defaults.CodableNSSecureCodingBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable {
public static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
extension Defaults.Serializable where Self: Codable & NSSecureCoding & Defaults.PreferNSSecureCoding {
public static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable & Codable {
extension Defaults.Serializable where Self: Codable & RawRepresentable {
public static var bridge: Defaults.RawRepresentableCodableBridge<Self> { Defaults.RawRepresentableCodableBridge() }
}
extension Defaults.Serializable where Self: Codable & RawRepresentable & Defaults.PreferRawRepresentable {
public static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable {
public static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
}
extension Defaults.Serializable where Self: NSSecureCoding {
public static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
}

View File

@ -100,3 +100,21 @@ public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializabl
/// Convenience protocol for `Codable`.
public protocol DefaultsCodableBridge: Defaults.Bridge where Serializable == String, Value: Codable {}
/**
Ambiguous bridge selector protocol. This lets you select your preferred bridge when there are multiple possibilities.
For example:
```
enum Interval: Int, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable {
case tenMinutes = 10
case halfHour = 30
case oneHour = 60
}
```
By default, if an `enum` conforms to `Codable` and `Defaults.Serializable`, it will use the `CodableBridge`, but by conforming to `Defaults.PreferRawRepresentable`, we can switch the bridge back to `RawRepresentableBridge`.
*/
public protocol DefaultsPreferRawRepresentable: RawRepresentable {}
public protocol DefaultsPreferNSSecureCoding: NSSecureCoding {}

View File

@ -19,6 +19,8 @@ public enum Defaults {
public typealias Serializable = DefaultsSerializable
public typealias CollectionSerializable = DefaultsCollectionSerializable
public typealias SetAlgebraSerializable = DefaultsSetAlgebraSerializable
public typealias PreferRawRepresentable = DefaultsPreferRawRepresentable
public typealias PreferNSSecureCoding = DefaultsPreferNSSecureCoding
public typealias Bridge = DefaultsBridge
typealias CodableBridge = DefaultsCodableBridge

View File

@ -8,6 +8,12 @@ private enum FixtureCodableEnum: String, Defaults.Serializable & Codable & Hasha
case oneHour = "1 Hour"
}
private enum FixtureCodableEnumPreferRawRepresentable: Int, Hashable, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable {
case tenMinutes = 10
case halfHour = 30
case oneHour = 60
}
extension Defaults.Keys {
fileprivate static let codableEnum = Key<FixtureCodableEnum>("codable_enum", default: .oneHour)
fileprivate static let codableEnumArray = Key<[FixtureCodableEnum]>("codable_enum", default: [.oneHour])
@ -118,6 +124,13 @@ final class DefaultsCodableEnumTests: XCTestCase {
XCTAssertEqual(Defaults[.codableEnumDictionary]["1"], .halfHour)
}
func testFixtureCodableEnumPreferRawRepresentable() {
let fixture: FixtureCodableEnumPreferRawRepresentable = .tenMinutes
let keyName = "testFixtureCodableEnumPreferRawRepresentable"
_ = Defaults.Key<FixtureCodableEnumPreferRawRepresentable>(keyName, default: fixture)
XCTAssertNotNil(UserDefaults.standard.integer(forKey: keyName))
}
@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, *)
func testObserveKeyCombine() {
let key = Defaults.Key<FixtureCodableEnum>("observeCodableEnumKeyCombine", default: .tenMinutes)

View File

@ -21,6 +21,19 @@ private final class UnicornCodableAndNSSecureCoding: NSObject, NSSecureCoding, C
}
}
@objc(UnicornCodableAndPreferNSSecureCoding)
private final class UnicornCodableAndPreferNSSecureCoding: NSObject, NSSecureCoding, Codable, Defaults.Serializable, Defaults.PreferNSSecureCoding {
static let supportsSecureCoding = true
func encode(with coder: NSCoder) {}
init?(coder: NSCoder) {}
override init() {
super.init()
}
}
extension Defaults.Keys {
fileprivate static let codable = Key<Unicorn>("codable", default: fixtureCodable)
fileprivate static let codableArray = Key<[Unicorn]>("codable", default: [fixtureCodable])
@ -135,7 +148,18 @@ final class DefaultsCodableTests: XCTestCase {
func testCodableAndNSSecureCoding() {
let fixture = UnicornCodableAndNSSecureCoding()
_ = Defaults.Key<UnicornCodableAndNSSecureCoding>("testCodableAndNSSecureCoding", default: fixture)
let keyName = "testCodableAndNSSecureCoding"
_ = Defaults.Key<UnicornCodableAndNSSecureCoding>(keyName, default: fixture)
XCTAssertNil(UserDefaults.standard.data(forKey: keyName))
XCTAssertNotNil(UserDefaults.standard.string(forKey: keyName))
}
func testCodableAndPreferNSSecureCoding() {
let fixture = UnicornCodableAndPreferNSSecureCoding()
let keyName = "testCodableAndPreferNSSecureCoding"
_ = Defaults.Key<UnicornCodableAndPreferNSSecureCoding>(keyName, default: fixture)
XCTAssertNil(UserDefaults.standard.string(forKey: keyName))
XCTAssertNotNil(UserDefaults.standard.data(forKey: keyName))
}
@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, *)

View File

@ -789,6 +789,27 @@ if let mimeType: mime = Defaults[.magic]["enum"]?.get() {
For more examples, see [Tests/DefaultsAnySerializableTests](./Tests/DefaultsTests/DefaultsAnySeriliazableTests.swift).
### Serialization for ambiguous `Codable` type
You may have a type that conforms to `Codable & NSSecureCoding` or a `Codable & RawRepresentable` enum. By default, `Defaults` will prefer the `Codable` conformance and use the `CodableBridge` to serialize it into a JSON string. If you want to serialize it as a `NSSecureCoding` data or use the raw value of the `RawRepresentable` enum, you can conform to `Defaults.PreferNSSecureCoding` or `Defaults.PreferRawRepresentable` to override the default bridge:
```swift
enum mime: String, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable {
case JSON = "application/json"
}
extension Defaults.Keys {
static let magic = Key<[String: Defaults.AnySerializable]>("magic", default: [:])
}
print(UserDefaults.standard.string(forKey: "magic"))
//=> application/json
```
Had we not added `Defaults.PreferRawRepresentable`, the stored representation would have been `"application/json"` instead of `application/json`.
This can also be useful if you conform a type you don't control to `Defaults.Serializable` as the type could receive `Codable` conformance at any time and then the stored representation would change, which could make the value unreadable. By explicitly defining which bridge to use, you ensure the stored representation will always stay the same.
### Custom `Collection` type
1. Create your `Collection` and make its elements conform to `Defaults.Serializable`.