Clean up some docs

This commit is contained in:
Sindre Sorhus 2021-05-16 19:42:48 +07:00
parent 534157f1b5
commit 4a0f5e7c3e
14 changed files with 419 additions and 464 deletions

View File

@ -8,7 +8,7 @@ Pod::Spec.new do |s|
s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' } s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' }
s.source = { :git => 'https://github.com/sindresorhus/Defaults.git', :tag => "v#{s.version}" } s.source = { :git => 'https://github.com/sindresorhus/Defaults.git', :tag => "v#{s.version}" }
s.source_files = 'Sources/**/*.swift' s.source_files = 'Sources/**/*.swift'
s.swift_version = '5.3' s.swift_version = '5.4'
s.macos.deployment_target = '10.12' s.macos.deployment_target = '10.12'
s.ios.deployment_target = '10.0' s.ios.deployment_target = '10.0'
s.tvos.deployment_target = '10.0' s.tvos.deployment_target = '10.0'

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.3 // swift-tools-version:5.4
import PackageDescription import PackageDescription
let package = Package( let package = Package(

View File

@ -33,16 +33,14 @@ extension Defaults.CodableBridge {
} }
/** /**
Any `Value` which protocol conforms to `Codable` and `Defaults.Serializable` will use `CodableBridge` Any `Value` that conforms to `Codable` and `Defaults.Serializable` will use `CodableBridge` to do the serialization and deserialization.
to do the serialization and deserialization.
*/ */
extension Defaults { extension Defaults {
public struct TopLevelCodableBridge<Value: Codable>: CodableBridge {} public struct TopLevelCodableBridge<Value: Codable>: CodableBridge {}
} }
/** /**
`RawRepresentableCodableBridge` is indeed because if `enum SomeEnum: String, Codable, Defaults.Serializable` `RawRepresentableCodableBridge` is needed because, for example, with `enum SomeEnum: String, Codable, Defaults.Serializable`, the compiler will be confused between `RawRepresentableBridge` and `TopLevelCodableBridge`.
the compiler will confuse between `RawRepresentableBridge` and `TopLevelCodableBridge`.
*/ */
extension Defaults { extension Defaults {
public struct RawRepresentableCodableBridge<Value: RawRepresentable & Codable>: CodableBridge {} public struct RawRepresentableCodableBridge<Value: RawRepresentable & Codable>: CodableBridge {}
@ -84,7 +82,7 @@ extension Defaults {
} }
// Version below macOS 10.13 and iOS 11.0 does not support `archivedData(withRootObject:requiringSecureCoding:)`. // Version below macOS 10.13 and iOS 11.0 does not support `archivedData(withRootObject:requiringSecureCoding:)`.
// We need to set `requiresSecureCoding` by ourself. // We need to set `requiresSecureCoding` ourselves.
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, *) { 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, *) {
return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true) return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true)
} else { } else {
@ -182,15 +180,7 @@ extension Defaults {
} }
/** /**
We need both `SetBridge` and `SetAlgebraBridge`. We need both `SetBridge` and `SetAlgebraBridge` because `Set` conforms to `Sequence` but `SetAlgebra` does not. `Set` conforms to `Sequence`, so we can convert it into an array with `Array.init<S>(S)` and store it in the `UserDefaults`. But `SetAlgebra` does not, so it is hard to convert it into an array. Thats why we need the `Defaults.SetAlgebraSerializable` protocol to convert it into an array.
Because `Set` conforms to `Sequence` but `SetAlgebra` not.
Set conforms to `Sequence`, so we can convert it into an array with `Array.init<S>(S)` and store it in the `UserDefaults`.
But `SetAlgebra` does not, so it is hard to convert it into an array.
Thats why we need `Defaults.SetAlgebraSerializable` protocol to convert it into an array.
*/ */
extension Defaults { extension Defaults {
public struct SetBridge<Element: Defaults.Serializable & Hashable>: Defaults.Bridge { public struct SetBridge<Element: Defaults.Serializable & Hashable>: Defaults.Bridge {

View File

@ -115,7 +115,6 @@ extension Set: Defaults.Serializable where Element: Defaults.Serializable {
public static var bridge: Defaults.SetBridge<Element> { Defaults.SetBridge() } public static var bridge: Defaults.SetBridge<Element> { Defaults.SetBridge() }
} }
extension Array: Defaults.Serializable where Element: Defaults.Serializable { extension Array: Defaults.Serializable where Element: Defaults.Serializable {
public static var isNativelySupportedType: Bool { Element.isNativelySupportedType } public static var isNativelySupportedType: Bool { Element.isNativelySupportedType }
public static var bridge: Defaults.ArrayBridge<Element> { Defaults.ArrayBridge() } public static var bridge: Defaults.ArrayBridge<Element> { Defaults.ArrayBridge() }
@ -127,9 +126,9 @@ extension Dictionary: Defaults.Serializable where Key: LosslessStringConvertible
} }
#if os(macOS) #if os(macOS)
/// `NSColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge` /// `NSColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`.
extension NSColor: Defaults.Serializable {} extension NSColor: Defaults.Serializable {}
#else #else
/// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge` /// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`.
extension UIColor: Defaults.Serializable {} extension UIColor: Defaults.Serializable {}
#endif #endif

View File

@ -1,9 +1,9 @@
import Foundation import Foundation
/** /**
All type that able to work with `Defaults` should conform this protocol. Types that conform to this protocol can be used with `Defaults`.
It should have a static variable bridge which protocol should conform to `Defaults.Bridge`. The type should have a static variable `bridge` which should reference an instance of a type that conforms to `Defaults.Bridge`.
``` ```
struct User { struct User {
@ -21,22 +21,22 @@ public protocol DefaultsSerializable {
typealias Serializable = Bridge.Serializable typealias Serializable = Bridge.Serializable
associatedtype Bridge: DefaultsBridge associatedtype Bridge: DefaultsBridge
/// Static bridge for the `Value` which cannot store natively /// Static bridge for the `Value` which cannot be stored natively.
static var bridge: Bridge { get } static var bridge: Bridge { get }
/// A flag to determine whether `Value` can be store natively or not /// A flag to determine whether `Value` can be stored natively or not.
static var isNativelySupportedType: Bool { get } static var isNativelySupportedType: Bool { get }
} }
/** /**
A Bridge can do the serialization and de-serialization. A `Bridge` is responsible for serialization and deserialization.
Have two associate types `Value` and `Serializable`. It has two associated types `Value` and `Serializable`.
- `Value`: the type user want to use it. - `Value`: The type you want to use.
- `Serializable`: the type stored in `UserDefaults`. - `Serializable`: The type stored in `UserDefaults`.
- `serialize`: will be executed before storing to the `UserDefaults` . - `serialize`: Executed before storing to the `UserDefaults` .
- `deserialize`: will be executed after retrieving its value from the `UserDefaults`. - `deserialize`: Executed after retrieving its value from the `UserDefaults`.
``` ```
struct User { struct User {
@ -44,6 +44,10 @@ struct User {
password: String password: String
} }
extension User {
static let bridge = UserBridge()
}
struct UserBridge: Defaults.Bridge { struct UserBridge: Defaults.Bridge {
typealias Value = User typealias Value = User
typealias Serializable = [String: String] typealias Serializable = [String: String]
@ -53,7 +57,10 @@ struct UserBridge: Defaults.Bridge {
return nil return nil
} }
return ["username": value.username, "password": value.password] return [
"username": value.username,
"password": value.password
]
} }
func deserialize(_ object: Serializable?) -> Value? { func deserialize(_ object: Serializable?) -> Value? {
@ -65,7 +72,10 @@ struct UserBridge: Defaults.Bridge {
return nil return nil
} }
return User(username: username, password: password) return User(
username: username,
password: password
)
} }
} }
``` ```
@ -75,12 +85,11 @@ public protocol DefaultsBridge {
associatedtype Serializable associatedtype Serializable
func serialize(_ value: Value?) -> Serializable? func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value? func deserialize(_ object: Serializable?) -> Value?
} }
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable { public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable {
/// `Collection` does not have initializer, but we need initializer to convert an array into the `Value` /// `Collection` does not have a initializer, but we need a initializer to convert an array into the `Value`.
init(_ elements: [Element]) init(_ elements: [Element])
} }
@ -89,5 +98,5 @@ public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializabl
func toArray() -> [Element] func toArray() -> [Element]
} }
/// Convenience protocol for `Codable` /// Convenience protocol for `Codable`.
public protocol DefaultsCodableBridge: DefaultsBridge where Serializable == String, Value: Codable {} public protocol DefaultsCodableBridge: DefaultsBridge where Serializable == String, Value: Codable {}

View File

@ -6,7 +6,7 @@ extension Defaults {
} }
/** /**
Migration the given key's value from json string to `Value`. Migrate the given key's value from JSON string to `Value`.
``` ```
extension Defaults.Keys { extension Defaults.Keys {

View File

@ -12,9 +12,7 @@ extension Data: Defaults.NativeType {
extension Data: Defaults.CodableType { extension Data: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Date: Defaults.NativeType { extension Date: Defaults.NativeType {
@ -23,9 +21,7 @@ extension Date: Defaults.NativeType {
extension Date: Defaults.CodableType { extension Date: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Bool: Defaults.NativeType { extension Bool: Defaults.NativeType {
@ -34,9 +30,7 @@ extension Bool: Defaults.NativeType {
extension Bool: Defaults.CodableType { extension Bool: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Int: Defaults.NativeType { extension Int: Defaults.NativeType {
@ -45,9 +39,7 @@ extension Int: Defaults.NativeType {
extension Int: Defaults.CodableType { extension Int: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension UInt: Defaults.NativeType { extension UInt: Defaults.NativeType {
@ -56,9 +48,7 @@ extension UInt: Defaults.NativeType {
extension UInt: Defaults.CodableType { extension UInt: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Double: Defaults.NativeType { extension Double: Defaults.NativeType {
@ -67,9 +57,7 @@ extension Double: Defaults.NativeType {
extension Double: Defaults.CodableType { extension Double: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Float: Defaults.NativeType { extension Float: Defaults.NativeType {
@ -78,9 +66,7 @@ extension Float: Defaults.NativeType {
extension Float: Defaults.CodableType { extension Float: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension String: Defaults.NativeType { extension String: Defaults.NativeType {
@ -89,9 +75,7 @@ extension String: Defaults.NativeType {
extension String: Defaults.CodableType { extension String: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension CGFloat: Defaults.NativeType { extension CGFloat: Defaults.NativeType {
@ -100,9 +84,7 @@ extension CGFloat: Defaults.NativeType {
extension CGFloat: Defaults.CodableType { extension CGFloat: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Int8: Defaults.NativeType { extension Int8: Defaults.NativeType {
@ -111,9 +93,7 @@ extension Int8: Defaults.NativeType {
extension Int8: Defaults.CodableType { extension Int8: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension UInt8: Defaults.NativeType { extension UInt8: Defaults.NativeType {
@ -122,9 +102,7 @@ extension UInt8: Defaults.NativeType {
extension UInt8: Defaults.CodableType { extension UInt8: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Int16: Defaults.NativeType { extension Int16: Defaults.NativeType {
@ -133,9 +111,7 @@ extension Int16: Defaults.NativeType {
extension Int16: Defaults.CodableType { extension Int16: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension UInt16: Defaults.NativeType { extension UInt16: Defaults.NativeType {
@ -144,9 +120,7 @@ extension UInt16: Defaults.NativeType {
extension UInt16: Defaults.CodableType { extension UInt16: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Int32: Defaults.NativeType { extension Int32: Defaults.NativeType {
@ -155,9 +129,7 @@ extension Int32: Defaults.NativeType {
extension Int32: Defaults.CodableType { extension Int32: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension UInt32: Defaults.NativeType { extension UInt32: Defaults.NativeType {
@ -166,9 +138,7 @@ extension UInt32: Defaults.NativeType {
extension UInt32: Defaults.CodableType { extension UInt32: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Int64: Defaults.NativeType { extension Int64: Defaults.NativeType {
@ -177,9 +147,7 @@ extension Int64: Defaults.NativeType {
extension Int64: Defaults.CodableType { extension Int64: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension UInt64: Defaults.NativeType { extension UInt64: Defaults.NativeType {
@ -188,9 +156,7 @@ extension UInt64: Defaults.NativeType {
extension UInt64: Defaults.CodableType { extension UInt64: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension URL: Defaults.NativeType { extension URL: Defaults.NativeType {
@ -199,9 +165,7 @@ extension URL: Defaults.NativeType {
extension URL: Defaults.CodableType { extension URL: Defaults.CodableType {
public typealias NativeForm = Self public typealias NativeForm = Self
public func toNative() -> Self { public func toNative() -> Self { self }
self
}
} }
extension Optional: Defaults.NativeType where Wrapped: Defaults.NativeType { extension Optional: Defaults.NativeType where Wrapped: Defaults.NativeType {

View File

@ -1,12 +1,14 @@
import Foundation import Foundation
/** /**
Only for migration. Only exists for migration.
Represents the type after migration and its protocol should conform to `Defaults.Serializable`. Represents the type after migration and its protocol should conform to `Defaults.Serializable`.
It should have an associated type name `CodableForm` which protocol conform to `Codable`. It should have an associated type name `CodableForm` where its protocol conform to `Codable`.
So we can convert the json string into `NativeType` like this.
So we can convert the JSON string into a `NativeType` like this:
``` ```
guard guard
let jsonString = string, let jsonString = string,
@ -24,13 +26,11 @@ public protocol DefaultsNativeType: Defaults.Serializable {
} }
/** /**
Only for migration. Only exists for migration.
Represents the type before migration an its protocol should conform to `Codable`. Represents the type before migration an its protocol should conform to `Codable`.
The main purposed of `CodableType` is trying to infer the `Codable` type to do `JSONDecoder().decode` The main purposed of `CodableType` is trying to infer the `Codable` type to do `JSONDecoder().decode`. It should have an associated type name `NativeForm` which is the type we want it to store in `UserDefaults`. nd it also have a `toNative()` function to convert itself into `NativeForm`.
It should have an associated type name `NativeForm` which is the type we want it to store in `UserDefaults`.
And it also have a `toNative()` function to convert itself into `NativeForm`.
``` ```
struct User { struct User {

View File

@ -14,18 +14,27 @@ extension UserDefaults {
} }
/** /**
Get json string in `UserDefaults` and decode it into the `NativeForm`. Get a JSON string in `UserDefaults` and decode it into its `NativeForm`.
How it works? How does it work?
For example:
Step1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
`JSONDecoder().decode([String].CodableForm.self, from: jsonData)`
Step2. `Array`conform to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`. 1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
`JSONDecoder().decode([String.CodableForm].self, from: jsonData)`
Step3. `String`'s `CodableForm` is `self`, because `String` is `Codable`. ```
`JSONDecoder().decode([String].self, from: jsonData)` JSONDecoder().decode([String].CodableForm.self, from: jsonData)
```
2. If `Array` conforms to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`.
```
JSONDecoder().decode([String.CodableForm].self, from: jsonData)
```
3. `String`'s `CodableForm` is `self`, because `String` is `Codable`.
```
JSONDecoder().decode([String].self, from: jsonData)
```
*/ */
func migrateCodableToNative<Value: Defaults.NativeType>(forKey key: String, of type: Value.Type) { func migrateCodableToNative<Value: Defaults.NativeType>(forKey key: String, of type: Value.Type) {
guard guard

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
extension Decodable { extension Decodable {
init?(jsonData: Data) { init?(jsonData: Data) {
guard let value = try? JSONDecoder().decode(Self.self, from: jsonData) else { guard let value = try? JSONDecoder().decode(Self.self, from: jsonData) else {
@ -147,6 +148,7 @@ extension DispatchQueue {
} }
} }
extension Sequence { extension Sequence {
/// Returns an array containing the non-nil elements. /// Returns an array containing the non-nil elements.
func compact<T>() -> [T] where Element == T? { func compact<T>() -> [T] where Element == T? {
@ -155,10 +157,12 @@ extension Sequence {
} }
} }
extension Defaults.Serializable { extension Defaults.Serializable {
/** /**
Cast `Serializable` value to `Self`. Cast a `Serializable` value to `Self`.
Convert the native support type from `UserDefaults` into `Self`.
Converts a natively supported type from `UserDefaults` into `Self`.
``` ```
guard let anyObject = object(forKey: key) else { guard let anyObject = object(forKey: key) else {
@ -169,7 +173,7 @@ extension Defaults.Serializable {
``` ```
*/ */
static func toValue(_ anyObject: Any) -> Self? { static func toValue(_ anyObject: Any) -> Self? {
// Return directly if `anyObject` can cast to Value, means `Value` is Native supported type. // Return directly if `anyObject` can cast to Value, since it means `Value` is a natively supported type.
if Self.isNativelySupportedType, let anyObject = anyObject as? Self { if Self.isNativelySupportedType, let anyObject = anyObject as? Self {
return anyObject return anyObject
} else if let value = Self.bridge.deserialize(anyObject as? Serializable) { } else if let value = Self.bridge.deserialize(anyObject as? Serializable) {
@ -181,14 +185,15 @@ extension Defaults.Serializable {
/** /**
Cast `Self` to `Serializable`. Cast `Self` to `Serializable`.
Convert `Self` into `UserDefaults` native support type.
Converts `Self` into `UserDefaults` native support type.
``` ```
set(Value.toSerialize(value), forKey: key) set(Value.toSerialize(value), forKey: key)
``` ```
*/ */
static func toSerializable(_ value: Self) -> Any? { static func toSerializable(_ value: Self) -> Any? {
// Return directly if `Self` is native supported type, since it does not need serialization. // Return directly if `Self` is a natively supported type, since it does not need serialization.
if Self.isNativelySupportedType { if Self.isNativelySupportedType {
return value return value
} else if let serialized = Self.bridge.serialize(value as? Self.Value) { } else if let serialized = Self.bridge.serialize(value as? Self.Value) {

View File

@ -2,7 +2,7 @@ import Defaults
import Foundation import Foundation
import XCTest import XCTest
// Create an unique id to test whether LosslessStringConvertible works. // Create an unique ID to test whether `LosslessStringConvertible` works.
private struct UniqueID: LosslessStringConvertible, Hashable { private struct UniqueID: LosslessStringConvertible, Hashable {
var id: Int64 var id: Int64
@ -25,7 +25,7 @@ private struct TimeZone: Hashable {
} }
extension TimeZone: Defaults.NativeType { extension TimeZone: Defaults.NativeType {
/// Associated `CodableForm` to `CodableTimeZone` /// Associated `CodableForm` to `CodableTimeZone`.
typealias CodableForm = CodableTimeZone typealias CodableForm = CodableTimeZone
static let bridge = TimeZoneBridge() static let bridge = TimeZoneBridge()
@ -37,7 +37,7 @@ private struct CodableTimeZone {
} }
extension CodableTimeZone: Defaults.CodableType { extension CodableTimeZone: Defaults.CodableType {
/// Convert from `Codable` to `Native` /// Convert from `Codable` to `Native`.
func toNative() -> TimeZone { func toNative() -> TimeZone {
TimeZone(id: id, name: name) TimeZone(id: id, name: name)
} }

View File

@ -6,7 +6,6 @@ import Defaults
let fixtureURL = URL(string: "https://sindresorhus.com")! let fixtureURL = URL(string: "https://sindresorhus.com")!
let fixtureFileURL = URL(string: "file://~/icon.png")! let fixtureFileURL = URL(string: "file://~/icon.png")!
let fixtureURL2 = URL(string: "https://example.com")! let fixtureURL2 = URL(string: "https://example.com")!
let fixtureDate = Date() let fixtureDate = Date()
extension Defaults.Keys { extension Defaults.Keys {

View File

@ -1,76 +1,68 @@
# Migration Guide From v4 to v5 # Migration guide from v4 to v5
## Warning **Warning: Test the migration thoroughly in your app. It might cause unintended data loss if you're not careful.**
If the migration is not success or incomplete. Edit `Defaults.Key` might cause data loss.
**Please back up your UserDefaults data before migration.**
## Summary ## Summary
Before v4, `Defaults` store `Codable` types as a JSON string. We have improved the stored representation of some types. Some types will require migration. Previously, all `Codable` types were serialized to a JSON string and stored as a `UserDefaults` string. `Defaults` is now able to store more types using the appropriate native `UserDefaults` type.
After v5, `Defaults` store `Defaults.Serializable` types with `UserDefaults` native supported type.
- Primitive types (`Int`, `Double`, `Bool`, `String`, etc) require no changes.
- Custom types (`struct`, `enum`, etc.) must now conform to `Defaults.Serializable` (in addition to `Codable`).
- `Array`, `Set`, and `Dictionary` will need to be manually migrated with `Defaults.migrate()`.
---
In v4, `Defaults` stored `Codable` types as a JSON string.\
In v5, `Defaults` stores many `Codable` types as native `UserDefaults` types.
```swift ```swift
// Before // v4
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0]) let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.string(forKey: "key") //=> "["0": 0]" UserDefaults.standard.string(forKey: "key")
//=> "[\"0\": 0]"
// After v5
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.dictionary(forKey: "key") //=> [0: 0]
``` ```
All types should conform to `Defaults.Serializable` in order to work with `Defaults`. ```swift
So this will require some migrations to resolve **TWO** major issues. // v5
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.dictionary(forKey: "key")
//=> [0: 0]
```
## Issues ## Issues
1. **Compiler complain that `Defaults.Key<Value>` is not conform to `Defaults.Serializable`.** 1. **The compiler complains that `Defaults.Key<Value>` does not conform to `Defaults.Serializable`.**
Since we replace `Codable` with `Defaults.Serializable`, `Key<Value>` will have to conform to `Value: Defaults.Serializable`. Since we replaced `Codable` with `Defaults.Serializable`, `Key<Value>` will have to conform to `Value: Defaults.Serializable`.
For this situation, please follow the guide below: For this situation, please follow the guides below:
- [From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5](#from-codable-struct-in-defaults-v4-to-codable-struct-in-defaults-v5) - [From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5](#from-codable-struct-in-defaults-v4-to-codable-struct-in-defaults-v5)
- [From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5](#from-codable-enum-in-defaults-v4-to-codable-enum-in-defaults-v5) - [From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5](#from-codable-enum-in-defaults-v4-to-codable-enum-in-defaults-v5)
2. **Previous value in UserDefaults is not readable. (ex. `Defaults[.array]` return `null`).** 2. **The previous value in `UserDefaults` is not readable. (for example, `Defaults[.array]` returns `nil`).**
In v5, `Defaults` reads value from `UserDefaults` as a native supported type. In v5, `Defaults` reads value from `UserDefaults` as a natively supported type, but since `UserDefaults` only contains JSON string before migration for `Codable` types, `Defaults` will not be able to work with it. For this situation, `Defaults` provides the `Defaults.migrate()` method to automate the migration process.
But `UserDefaults` only contains JSON string before migration, `Defaults` will not be able to work with it. - [From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set`(with natively supported elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-native-supported-elements-in-defaults-v5)
For this situation, `Defaults` provides `Defaults.migrate` method to automate the migration process. - [From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with codable elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-codable-elements-in-defaults-v5)
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-native-supported-elements-in-defaults-v5)
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-codable-elements-in-defaults-v5)
**Caution:**
- This is a breaking change, there is no way to convert it back to `Codable Array/Dictionary/Set` so far.
- **Optional migration**
`Defaults` also provide a migration guide to let users convert them `Codable` things into the UserDefaults native supported type, but it is optional.
- [From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5](#from-codable-enum-in-defaults-v4-to-rawrepresentable-in-defaults-v5-optional)
- [From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5](#from-codable-struct-in-defaults-v4-to-dictionary-in-defaults-v5-optional)
## Testing ## Testing
We recommend user doing some tests after migration. We recommend doing some manual testing after migrating.
The most critical issue is the second one (Previous value in UserDefaults is not readable).
After migration, there is a need to make sure user can get the same value as before.
You can try to test it manually or making a test file to test it.
Here is the guide for making a migration test: For example, let's say you are trying to migrate an array of `Codable` string to a native array.
For example you are trying to migrate a `Codable String` array to native array.
1. Get previous value in UserDefaults (using `defaults` command or whatever you want). 1. Get the previous value in `UserDefaults` (using `defaults` command or whatever you want).
```swift ```swift
let string = "[\"a\",\"b\",\"c\"]" let string = "[\"a\",\"b\",\"c\"]"
``` ```
2. Insert the value above into UserDefaults. 2. Insert the above value into `UserDefaults`.
```swift ```swift
UserDefaults.standard.set(string, forKey: "testKey") UserDefaults.standard.set(string, forKey: "testKey")
``` ```
3. Call `Defaults.migrate` and then using `Defaults` to get its value 3. Call `Defaults.migrate()` and then use `Defaults` to get its value.
```swift ```swift
let key = Defaults.Key<[String]>("testKey", default: []) let key = Defaults.Key<[String]>("testKey", default: [])
@ -79,15 +71,15 @@ Defaults.migrate(key, to: .v5)
Defaults[key] //=> [a, b, c] Defaults[key] //=> [a, b, c]
``` ```
--- ## Migrations
### From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5 ### From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5
Before v4, `struct` have to conform to `Codable` to store it as a JSON string. In v4, `struct` had to conform to `Codable` to store it as a JSON string.
After v5, `struct` have to conform to `Defaults.Serializable & Codable` to store it as a JSON string. In v5, `struct` has to conform to `Codable` and `Defaults.Serializable` to store it as a JSON string.
#### Before migration, your code should be like this #### Before migration
```swift ```swift
private struct TimeZone: Codable { private struct TimeZone: Codable {
@ -102,10 +94,10 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. Let `TimeZone` conform to `Defaults.Serializable`. 1. Make `TimeZone` conform to `Defaults.Serializable`.
```swift ```swift
private struct TimeZone: Defaults.Serializable, Codable { private struct TimeZone: Codable, Defaults.Serializable {
var id: String var id: String
var name: String var name: String
} }
@ -113,15 +105,13 @@ private struct TimeZone: Defaults.Serializable, Codable {
2. Now `Defaults[.timezone]` should be readable. 2. Now `Defaults[.timezone]` should be readable.
---
### From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5 ### From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5
Before v4, `enum` have to conform to `Codable` to store it as a JSON string. In v4, `enum` had to conform to `Codable` to store it as a JSON string.
After v5, struct have to conform to `Defaults.Serializable & Codable` to store it as a JSON string. In v5, `enum` has to conform to `Codable` and `Defaults.Serializable` to store it as a JSON string.
#### Before migration, your code should be like this #### Before migration
```swift ```swift
private enum Period: String, Codable { private enum Period: String, Codable {
@ -137,7 +127,7 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. Let `Period` conform to `Defaults.Serializable`. 1. Make `Period` conform to `Defaults.Serializable`.
```swift ```swift
private enum Period: String, Defaults.Serializable, Codable { private enum Period: String, Defaults.Serializable, Codable {
@ -149,15 +139,13 @@ private enum Period: String, Defaults.Serializable, Codable {
2. Now `Defaults[.period]` should be readable. 2. Now `Defaults[.period]` should be readable.
--- ### From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with natively supported elements) in Defaults v5
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5 In v4, `Defaults` stored array/dictionary as a JSON string: `["a", "b", "c"]`.
Before v4, `Defaults` will store array/dictionary as a JSON string(`["a", "b", "c"]`). In v5, `Defaults` stores it as a native array/dictionary with natively supported elements: `[a, b, c]`.
After v5, `Defaults` will store it as a native array/dictionary with native supported elements(`[a, b, c]` ). #### Before migration
#### Before migration, your code should be like this
```swift ```swift
extension Defaults.Keys { extension Defaults.Keys {
@ -170,25 +158,24 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. **Call `Defaults.migration(.arrayString, to: .v5)`, `Defaults.migration(.setString, to: .v5)`, `Defaults.migration(.dictionaryStringInt, to: .v5)`, `Defaults.migration(.dictionaryStringIntInArray, to: .v5)`.** 1. **Call `Defaults.migrate(.arrayString, to: .v5)`, `Defaults.migrate(.setString, to: .v5)`, `Defaults.migrate(.dictionaryStringInt, to: .v5)`, `Defaults.migrate(.dictionaryStringIntInArray, to: .v5)`.**
2. Now `Defaults[.arrayString]`, `Defaults.[.setString]`, `Defaults[.dictionaryStringInt]`, `Defaults[.dictionaryStringIntInArray]` should be readable. 2. Now `Defaults[.arrayString]`, `Defaults.[.setString]`, `Defaults[.dictionaryStringInt]`, `Defaults[.dictionaryStringIntInArray]` should be readable.
--- ### From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with `Codable` elements) in Defaults v5
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5 In v4, `Defaults` would store array/dictionary as a single JSON string: `"{ "id": "0", "name": "Asia/Taipei" }"`, `"["10 Minutes", "30 Minutes"]"`.
Before v4, `Defaults` will store array/dictionary as a JSON string(`"{ "id": "0", "name": "Asia/Taipei" }"`, `"["10 Minutes", "30 Minutes"]"`). In v5, `Defaults` will store it as a native array/dictionary with `Codable` elements: `{ id: 0, name: Asia/Taipei }`, `[10 Minutes, 30 Minutes]`.
After v5, `Defaults` will store it as a native array/dictionary with codable elements(`{ id: 0, name: Asia/Taipei }`, `[10 Minutes, 30 Minutes]`). #### Before migration
#### Before migration, your code should be like this
```swift ```swift
private struct TimeZone: Codable, Hashable { private struct TimeZone: Hashable, Codable {
var id: String var id: String
var name: String var name: String
} }
private enum Period: String, Codable, Hashable {
private enum Period: String, Hashable, Codable {
case tenMinutes = "10 Minutes" case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes" case halfHour = "30 Minutes"
case oneHour = "1 Hour" case oneHour = "1 Hour"
@ -206,33 +193,35 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. Let `TimeZone` and `Period` conform to `Defaults.Serializable` 1. Make `TimeZone` and `Period` conform to `Defaults.Serializable`.
```swift ```swift
private struct TimeZone: Defaults.Serializable, Codable, Hashable { private struct TimeZone: Hashable, Codable, Defaults.Serializable {
var id: String var id: String
var name: String var name: String
} }
private enum Period: String, Defaults.Serializable, Codable, Hashable { private enum Period: String, Hashable, Codable, Defaults.Serializable {
case tenMinutes = "10 Minutes" case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes" case halfHour = "30 Minutes"
case oneHour = "1 Hour" case oneHour = "1 Hour"
} }
``` ```
2. **Call `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`, `Defaults.migration(.arrayPeriod, to: .v5)`, `Defaults.migration(.setPeriod, to: .v5)` , `Defaults.migration(.dictionaryPeriod, to: .v5)`.** 2. **Call `Defaults.migrate(.arrayTimezone, to: .v5)`, `Defaults.migrate(.setTimezone, to: .v5)`, `Defaults.migrate(.dictionaryTimezone, to: .v5)`, `Defaults.migrate(.arrayPeriod, to: .v5)`, `Defaults.migrate(.setPeriod, to: .v5)` , `Defaults.migrate(.dictionaryPeriod, to: .v5)`.**
3. Now `Defaults[.arrayTimezone]`, `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]`, `Defaults[.arrayPeriod]`, `Defaults[.setPeriod]` , `Defaults[.dictionaryPeriod]` should be readable. 3. Now `Defaults[.arrayTimezone]`, `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]`, `Defaults[.arrayPeriod]`, `Defaults[.setPeriod]` , `Defaults[.dictionaryPeriod]` should be readable.
--- ---
### From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5 (Optional) ## Optional migrations
Before v4, `Defaults` will store `enum` as a JSON string(`"10 Minutes"`). ### From `Codable` enum in Defaults v4 to `RawRepresentable` enum in Defaults v5 *(Optional)*
After v5, `Defaults` will store `enum` as a `RawRepresentable`(`10 Minutes`). In v4, `Defaults` will store `enum` as a JSON string: `"10 Minutes"`.
#### Before migration, your code should be like this In v5, `Defaults` can store `enum` as a native string: `10 Minutes`.
#### Before migration
```swift ```swift
private enum Period: String, Codable { private enum Period: String, Codable {
@ -248,7 +237,7 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. Create an enum call `CodablePeriod` and create an extension of it. Let it conform to `Defaults.CodableType` and associated `NativeForm` to `Period`. 1. Create another enum called `CodablePeriod` and create an extension of it. Make the extension conform to `Defaults.CodableType` and its associated type `NativeForm` to `Period`.
```swift ```swift
private enum CodablePeriod: String { private enum CodablePeriod: String {
@ -262,7 +251,7 @@ extension CodablePeriod: Defaults.CodableType {
} }
``` ```
2. Remove `Codable`. So `Period` can be stored natively. 2. Remove `Codable` conformance so `Period` can be stored natively.
```swift ```swift
private enum Period: String { private enum Period: String {
@ -272,7 +261,7 @@ private enum Period: String {
} }
``` ```
3. Create an extension of `Period`, let it conform to `Defaults.NativeType` and its `CodableForm` should be `CodablePeriod`. 3. Create an extension of `Period` that conforms to `Defaults.NativeType`. Its `CodableForm` should be `CodablePeriod`.
```swift ```swift
extension Period: Defaults.NativeType { extension Period: Defaults.NativeType {
@ -280,12 +269,10 @@ extension Period: Defaults.NativeType {
} }
``` ```
4. **Call `Defaults.migration(.period)`** 4. **Call `Defaults.migrate(.period)`**
5. Now `Defaults[.period]` should be readable. 5. Now `Defaults[.period]` should be readable.
* hints: You can also implement `toNative` function at `Defaults.CodableType` in your own way. You can also instead implement the `toNative` function in `Defaults.CodableType` for flexibility:
For example
```swift ```swift
extension CodablePeriod: Defaults.CodableType { extension CodablePeriod: Defaults.CodableType {
@ -304,14 +291,11 @@ extension CodablePeriod: Defaults.CodableType {
} }
``` ```
### From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5 *(Optional)*
--- This happens when you have a struct which is stored as a `Codable` JSON string before, but now you want it to be stored as a native `UserDefaults` dictionary.
### From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5 (Optional) #### Before migration
This happens when you have a struct which is stored as a codable JSON string before, but now you want it to be stored as a native UserDefaults dictionary.
#### Before migration, your code should be like this
```swift ```swift
private struct TimeZone: Codable { private struct TimeZone: Codable {
@ -329,7 +313,7 @@ extension Defaults.Keys {
#### Migration steps #### Migration steps
1. Create a `TimeZoneBridge` which conform to `Defaults.Bridge` and its `Value` is TimeZone, `Serializable` is `[String: String]`. 1. Create a `TimeZoneBridge` which conforms to `Defaults.Bridge` and its `Value` is `TimeZone` and `Serializable` is `[String: String]`.
```swift ```swift
private struct TimeZoneBridge: Defaults.Bridge { private struct TimeZoneBridge: Defaults.Bridge {
@ -341,7 +325,10 @@ private struct TimeZoneBridge: Defaults.Bridge {
return nil return nil
} }
return ["id": value.id, "name": value.name] return [
"id": value.id,
"name": value.name
]
} }
func deserialize(_ object: Serializable?) -> TimeZone? { func deserialize(_ object: Serializable?) -> TimeZone? {
@ -353,12 +340,15 @@ private struct TimeZoneBridge: Defaults.Bridge {
return nil return nil
} }
return TimeZone(id: id, name: name) return TimeZone(
id: id,
name: name
)
} }
} }
``` ```
2. Create an extension of `TimeZone`, let it conform to `Defaults.NativeType` and its static bridge is `TimeZoneBridge`(Compiler will complain that `TimeZone` is not conform to `Defaults.NativeType`, will resolve it later). 2. Create an extension of `TimeZone` that conforms to `Defaults.NativeType` and its static bridge is `TimeZoneBridge`. The compiler will complain that `TimeZone` does not conform to `Defaults.NativeType`. We will resolve that later.
```swift ```swift
private struct TimeZone: Hashable { private struct TimeZone: Hashable {
@ -371,7 +361,7 @@ extension TimeZone: Defaults.NativeType {
} }
``` ```
3. Create an extension of `CodableTimeZone` and let it conform to `Defaults.CodableType` 3. Create an extension of `CodableTimeZone` that conforms to `Defaults.CodableType`.
```swift ```swift
private struct CodableTimeZone { private struct CodableTimeZone {
@ -380,7 +370,7 @@ private struct CodableTimeZone {
} }
extension CodableTimeZone: Defaults.CodableType { extension CodableTimeZone: Defaults.CodableType {
/// Convert from `Codable` to `Native` /// Convert from `Codable` to native type.
func toNative() -> TimeZone { func toNative() -> TimeZone {
TimeZone(id: id, name: name) TimeZone(id: id, name: name)
} }
@ -391,14 +381,13 @@ extension CodableTimeZone: Defaults.CodableType {
```swift ```swift
extension TimeZone: Defaults.NativeType { extension TimeZone: Defaults.NativeType {
/// Associated `CodableForm` to `CodableTimeZone`
typealias CodableForm = CodableTimeZone typealias CodableForm = CodableTimeZone
static let bridge = TimeZoneBridge() static let bridge = TimeZoneBridge()
} }
``` ```
5. **Call `Defaults.migration(.timezone, to: .v5)`, `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`**. 5. **Call `Defaults.migrate(.timezone, to: .v5)`, `Defaults.migrate(.arrayTimezone, to: .v5)`, `Defaults.migrate(.setTimezone, to: .v5)`, `Defaults.migrate(.dictionaryTimezone, to: .v5)`**.
6. Now `Defaults[.timezone]`, `Defaults[.arrayTimezone]` , `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]` should be readable. 6. Now `Defaults[.timezone]`, `Defaults[.arrayTimezone]` , `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]` should be readable.
**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.** **See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.**

485
readme.md
View File

@ -52,26 +52,25 @@ pod 'Defaults'
## Support types ## Support types
| Single Value | - `Int(8/16/32/64)`
|:------------------:| - `UInt(8/16/32/64)`
| `Int(8/16/32/64)` | - `Double`
| `UInt(8/16/32/64)` | - `CGFloat`
| `Double` | - `Float`
| `Float` | - `String`
| `String` | - `Bool`
| `CGFloat` | - `Date`
| `Bool` | - `Data`
| `Date` | - `URL`
| `Data` | - `NSColor` (macOS)
| `URL` | - `UIColor` (iOS)
| `NSColor` (macOS) | - `Codable`
| `UIColor` (iOS) |
| `Codable` |
The list above only show the type that does not need further more configuration. Defaults also support the above types wrapped in `Array`, `Set`, `Dictionary`, and even wrapped in nested types. For example, `[[String: Set<[String: Int]>]]`.
We also support them wrapped in `Array`, `Set`, `Dictionary` even wrapped in nested type. ex. `[[String: Set<[String: Int]>]]`.
For more types, see [Enum Example](#enum-example), [Codable Example](#codable-example) or [Advanced Usage](#advanced-usage). For more types, see the [enum example](#enum-example), [`Codable` example](#codable-example), or [advanced Usage](#advanced-usage). For more examples, see [Tests/DefaultsTests](./Tests/DefaultsTests).
For more examples, see [Tests/DefaultsTests](./Tests/DefaultsTests).
You can easily add support for any custom type.
## Usage ## Usage
@ -137,7 +136,9 @@ Defaults[.defaultDuration].rawValue
//=> "1 Hour" //=> "1 Hour"
``` ```
### Codable Example *(This works as long as the raw value of the enum is any of the supported types)*
### Codable example
```swift ```swift
struct User: Codable, Defaults.Serializable { struct User: Codable, Defaults.Serializable {
@ -339,217 +340,6 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
//=> true //=> true
``` ```
## Advanced Usage
### Serialization of custom types
Although `Defaults` already support many types internal, there might have some situations where you want to use your own type.
The guide below will show you how to make your own custom type works with `Defaults`.
1. Create your own custom type.
```swift
struct User {
let name: String
let age: String
}
```
2. Create a bridge which protocol conforms to `Defaults.Bridge`.
```swift
struct UserBridge: Defaults.Bridge {
typealias Value = User
typealias Serializable = [String: String]
public func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["name": value.name, "age": value.age]
}
public func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let name = object["name"],
let age = object["age"]
else {
return nil
}
return User(name: name, age: age)
}
}
```
3. Create an extension of `User`, let its protocol conforms to `Defaults.Serializable` and its static bridge should be the bridge we created above.
```swift
struct User {
let name: String
let age: String
}
extension User: Defaults.Serializable {
static let bridge = UserBridge()
}
```
4. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let user = Defaults.Key<User>("user", default: User(name: "Hello", age: "24"))
static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")])
static let setUser = Defaults.Key<Set<User>>("user", default: Set([User(name: "Hello", age: "24")]))
static let dictionaryUser = Defaults.Key<[String: User]>("dictionaryUser", default: ["user": User(name: "Hello", age: "24")])
}
Defaults[.user].name //=> "Hello"
Defaults[.arrayUser][0].name //=> "Hello"
Defaults[.setUser].first?.name //=> "Hello"
Defaults[.dictionaryUser]["user"]?.name //=> "Hello"
```
### Serialization of Collection
1. Create your Collection and its element should conforms to `Defaults.Serializable`.
```swift
struct Bag<Element: Defaults.Serializable>: Collection {
var items: [Element]
var startIndex: Int {
items.startIndex
}
var endIndex: Int {
items.endIndex
}
mutating func insert(element: Element, at: Int) {
items.insert(element, at: at)
}
func index(after index: Int) -> Int {
items.index(after: index)
}
subscript(position: Int) -> Element {
items[position]
}
}
```
2. Create an extension of `Bag`. let it conforms to `Defaults.CollectionSerializable`
```swift
extension Bag: Defaults.CollectionSerializable {
init(_ elements: [Element]) {
self.items = elements
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"]))
}
Defaults[.stringBag][0] //=> "Hello"
Defaults[.stringBag][1] //=> "World!"
```
### Serialization of SetAlgebra
1. Create your SetAlgebra and its element should conforms to `Defaults.Serializable & Hashable`
```swift
struct SetBag<Element: Defaults.Serializable & Hashable>: SetAlgebra {
var store = Set<Element>()
init() {}
init(_ store: Set<Element>) {
self.store = store
}
func contains(_ member: Element) -> Bool {
store.contains(member)
}
func union(_ other: SetBag) -> SetBag {
SetBag(store.union(other.store))
}
func intersection(_ other: SetBag)
-> SetBag {
var setBag = SetBag()
setBag.store = store.intersection(other.store)
return setBag
}
func symmetricDifference(_ other: SetBag)
-> SetBag {
var setBag = SetBag()
setBag.store = store.symmetricDifference(other.store)
return setBag
}
@discardableResult
mutating func insert(_ newMember: Element)
-> (inserted: Bool, memberAfterInsert: Element) {
store.insert(newMember)
}
mutating func remove(_ member: Element) -> Element? {
store.remove(member)
}
mutating func update(with newMember: Element) -> Element? {
store.update(with: newMember)
}
mutating func formUnion(_ other: SetBag) {
store.formUnion(other.store)
}
mutating func formSymmetricDifference(_ other: SetBag) {
store.formSymmetricDifference(other.store)
}
mutating func formIntersection(_ other: SetBag) {
store.formIntersection(other.store)
}
}
```
2. Create an extension of `SetBag`. Let it conforms to `Defaults.SetAlgebraSerializable`
```swift
extension SetBag: Defaults.SetAlgebraSerializable {
func toArray() -> [Element] {
Array(store)
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringSet = Key<SetBag<String>>("stringSet", default: SetBag(["Hello", "World!"]))
}
Defaults[.stringSet].contains("Hello") //=> true
Defaults[.stringSet].contains("World!") //=> true
```
## API ## API
### `Defaults` ### `Defaults`
@ -586,9 +376,9 @@ public protocol DefaultsSerializable {
Type: `protocol` Type: `protocol`
All types conform to this protocol will be able to work with `Defaults`. Types that conform to this protocol can be used with `Defaults`.
It should have a static variable `bridge` which protocol should conform to `Defaults.Bridge`. The type should have a static variable `bridge` which should reference an instance of a type that conforms to `Defaults.Bridge`.
#### `Defaults.Bridge` #### `Defaults.Bridge`
@ -596,6 +386,7 @@ It should have a static variable `bridge` which protocol should conform to `Defa
public protocol DefaultsBridge { public protocol DefaultsBridge {
associatedtype Value associatedtype Value
associatedtype Serializable associatedtype Serializable
func serialize(_ value: Value?) -> Serializable? func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value? func deserialize(_ object: Serializable?) -> Value?
} }
@ -603,17 +394,14 @@ public protocol DefaultsBridge {
Type: `protocol` Type: `protocol`
A Bridge can do serialization and de-serialization. A `Bridge` is responsible for serialization and deserialization.
Have two associate types `Value` and `Serializable`. It has two associated types `Value` and `Serializable`.
`Value` is the type user want to use it. - `Value`: The type you want to use.
- `Serializable`: The type stored in `UserDefaults`.
`Serializable` is the type stored in `UserDefaults`. - `serialize`: Executed before storing to the `UserDefaults` .
- `deserialize`: Executed after retrieving its value from the `UserDefaults`.
`serialize` will be executed before storing to the `UserDefaults` .
`deserialize` will be executed after retrieving its value from the `UserDefaults`.
#### `Defaults.reset(keys…)` #### `Defaults.reset(keys…)`
@ -621,8 +409,6 @@ Type: `func`
Reset the given keys back to their default values. 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. 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` #### `Defaults.observe`
@ -740,11 +526,9 @@ Type: `func`
Migrate the given keys to the specific version. Migrate the given keys to the specific version.
You can specify up to 10 keys. If you need to specify more, call this method multiple times.
### `@Default(_ key:)` ### `@Default(_ key:)`
Get/set a `Defaults` item and also have the view be updated when the value changes. Get/set a `Defaults` item and also have the SwiftUI view be updated when the value changes.
### Advanced ### Advanced
@ -776,6 +560,213 @@ A `SetAlgebra` which can store into the native `UserDefaults`.
It should have a function `func toArray() -> [Element]` to let `Defaults` do the serialization. It should have a function `func toArray() -> [Element]` to let `Defaults` do the serialization.
## Advanced usage
### Custom types
Although `Defaults` already has built-in support for many types, you might need to be able to use your own custom type. The below guide will show you how to make your own custom type work with `Defaults`.
1. Create your own custom type.
```swift
struct User {
let name: String
let age: String
}
```
2. Create a bridge that conforms to `Defaults.Bridge`, which is responsible for handling serialization and deserialization.
```swift
struct UserBridge: Defaults.Bridge {
typealias Value = User
typealias Serializable = [String: String]
public func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return [
"name": value.name,
"age": value.age
]
}
public func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let name = object["name"],
let age = object["age"]
else {
return nil
}
return User(
name: name,
age: age
)
}
}
```
3. Create an extension of `User` that conforms to `Defaults.Serializable`. Its static bridge should be the bridge we created above.
```swift
struct User {
let name: String
let age: String
}
extension User: Defaults.Serializable {
static let bridge = UserBridge()
}
```
4. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let user = Defaults.Key<User>("user", default: User(name: "Hello", age: "24"))
static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")])
static let setUser = Defaults.Key<Set<User>>("user", default: Set([User(name: "Hello", age: "24")]))
static let dictionaryUser = Defaults.Key<[String: User]>("dictionaryUser", default: ["user": User(name: "Hello", age: "24")])
}
Defaults[.user].name //=> "Hello"
Defaults[.arrayUser][0].name //=> "Hello"
Defaults[.setUser].first?.name //=> "Hello"
Defaults[.dictionaryUser]["user"]?.name //=> "Hello"
```
### Custom `Collection` type
1. Create your `Collection` and make its elements conform to `Defaults.Serializable`.
```swift
struct Bag<Element: Defaults.Serializable>: Collection {
var items: [Element]
var startIndex: Int { items.startIndex }
var endIndex: Int { items.endIndex }
mutating func insert(element: Element, at: Int) {
items.insert(element, at: at)
}
func index(after index: Int) -> Int {
items.index(after: index)
}
subscript(position: Int) -> Element {
items[position]
}
}
```
2. Create an extension of `Bag` that conforms to `Defaults.CollectionSerializable`.
```swift
extension Bag: Defaults.CollectionSerializable {
init(_ elements: [Element]) {
self.items = elements
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"]))
}
Defaults[.stringBag][0] //=> "Hello"
Defaults[.stringBag][1] //=> "World!"
```
### Custom `SetAlgebra` type
1. Create your `SetAlgebra` and make its elements conform to `Defaults.Serializable & Hashable`
```swift
struct SetBag<Element: Defaults.Serializable & Hashable>: SetAlgebra {
var store = Set<Element>()
init() {}
init(_ store: Set<Element>) {
self.store = store
}
func contains(_ member: Element) -> Bool {
store.contains(member)
}
func union(_ other: SetBag) -> SetBag {
SetBag(store.union(other.store))
}
func intersection(_ other: SetBag) -> SetBag {
var setBag = SetBag()
setBag.store = store.intersection(other.store)
return setBag
}
func symmetricDifference(_ other: SetBag) -> SetBag {
var setBag = SetBag()
setBag.store = store.symmetricDifference(other.store)
return setBag
}
@discardableResult
mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
store.insert(newMember)
}
mutating func remove(_ member: Element) -> Element? {
store.remove(member)
}
mutating func update(with newMember: Element) -> Element? {
store.update(with: newMember)
}
mutating func formUnion(_ other: SetBag) {
store.formUnion(other.store)
}
mutating func formSymmetricDifference(_ other: SetBag) {
store.formSymmetricDifference(other.store)
}
mutating func formIntersection(_ other: SetBag) {
store.formIntersection(other.store)
}
}
```
2. Create an extension of `SetBag` that conforms to `Defaults.SetAlgebraSerializable`
```swift
extension SetBag: Defaults.SetAlgebraSerializable {
func toArray() -> [Element] {
Array(store)
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringSet = Key<SetBag<String>>("stringSet", default: SetBag(["Hello", "World!"]))
}
Defaults[.stringSet].contains("Hello") //=> true
Defaults[.stringSet].contains("World!") //=> true
```
## FAQ ## FAQ
### How can I store a dictionary of arbitrary values? ### How can I store a dictionary of arbitrary values?