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.source = { :git => 'https://github.com/sindresorhus/Defaults.git', :tag => "v#{s.version}" }
s.source_files = 'Sources/**/*.swift'
s.swift_version = '5.3'
s.swift_version = '5.4'
s.macos.deployment_target = '10.12'
s.ios.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
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`
to do the serialization and deserialization.
Any `Value` that conforms to `Codable` and `Defaults.Serializable` will use `CodableBridge` to do the serialization and deserialization.
*/
extension Defaults {
public struct TopLevelCodableBridge<Value: Codable>: CodableBridge {}
}
/**
`RawRepresentableCodableBridge` is indeed because if `enum SomeEnum: String, Codable, Defaults.Serializable`
the compiler will confuse between `RawRepresentableBridge` and `TopLevelCodableBridge`.
`RawRepresentableCodableBridge` is needed because, for example, with `enum SomeEnum: String, Codable, Defaults.Serializable`, the compiler will be confused between `RawRepresentableBridge` and `TopLevelCodableBridge`.
*/
extension Defaults {
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:)`.
// 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, *) {
return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true)
} else {
@ -182,15 +180,7 @@ extension Defaults {
}
/**
We need both `SetBridge` and `SetAlgebraBridge`.
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.
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.
*/
extension Defaults {
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() }
}
extension Array: Defaults.Serializable where Element: Defaults.Serializable {
public static var isNativelySupportedType: Bool { Element.isNativelySupportedType }
public static var bridge: Defaults.ArrayBridge<Element> { Defaults.ArrayBridge() }
@ -127,9 +126,9 @@ extension Dictionary: Defaults.Serializable where Key: LosslessStringConvertible
}
#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 {}
#else
/// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`
/// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`.
extension UIColor: Defaults.Serializable {}
#endif

View File

@ -1,9 +1,9 @@
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 {
@ -21,22 +21,22 @@ public protocol DefaultsSerializable {
typealias Serializable = Bridge.Serializable
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 }
/// 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 }
}
/**
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.
- `Serializable`: the type stored in `UserDefaults`.
- `serialize`: will be executed before storing to the `UserDefaults` .
- `deserialize`: will be executed after retrieving its value from the `UserDefaults`.
- `Value`: The type you want to use.
- `Serializable`: The type stored in `UserDefaults`.
- `serialize`: Executed before storing to the `UserDefaults` .
- `deserialize`: Executed after retrieving its value from the `UserDefaults`.
```
struct User {
@ -44,6 +44,10 @@ struct User {
password: String
}
extension User {
static let bridge = UserBridge()
}
struct UserBridge: Defaults.Bridge {
typealias Value = User
typealias Serializable = [String: String]
@ -53,7 +57,10 @@ struct UserBridge: Defaults.Bridge {
return nil
}
return ["username": value.username, "password": value.password]
return [
"username": value.username,
"password": value.password
]
}
func deserialize(_ object: Serializable?) -> Value? {
@ -65,7 +72,10 @@ struct UserBridge: Defaults.Bridge {
return nil
}
return User(username: username, password: password)
return User(
username: username,
password: password
)
}
}
```
@ -75,12 +85,11 @@ public protocol DefaultsBridge {
associatedtype Serializable
func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value?
}
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])
}
@ -89,5 +98,5 @@ public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializabl
func toArray() -> [Element]
}
/// Convenience protocol for `Codable`
/// Convenience protocol for `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 {

View File

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

View File

@ -1,12 +1,14 @@
import Foundation
/**
Only for migration.
Only exists for migration.
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`.
So we can convert the json string into `NativeType` like this.
It should have an associated type name `CodableForm` where its protocol conform to `Codable`.
So we can convert the JSON string into a `NativeType` like this:
```
guard
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`.
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`.
And it also have a `toNative()` function to convert itself into `NativeForm`.
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`.
```
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?
For example:
Step1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
`JSONDecoder().decode([String].CodableForm.self, from: jsonData)`
How does it work?
Step2. `Array`conform to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`.
`JSONDecoder().decode([String.CodableForm].self, from: jsonData)`
1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
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) {
guard

View File

@ -1,5 +1,6 @@
import Foundation
extension Decodable {
init?(jsonData: Data) {
guard let value = try? JSONDecoder().decode(Self.self, from: jsonData) else {
@ -147,6 +148,7 @@ extension DispatchQueue {
}
}
extension Sequence {
/// Returns an array containing the non-nil elements.
func compact<T>() -> [T] where Element == T? {
@ -155,10 +157,12 @@ extension Sequence {
}
}
extension Defaults.Serializable {
/**
Cast `Serializable` value to `Self`.
Convert the native support type from `UserDefaults` into `Self`.
Cast a `Serializable` value to `Self`.
Converts a natively supported type from `UserDefaults` into `Self`.
```
guard let anyObject = object(forKey: key) else {
@ -169,7 +173,7 @@ extension Defaults.Serializable {
```
*/
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 {
return anyObject
} else if let value = Self.bridge.deserialize(anyObject as? Serializable) {
@ -181,14 +185,15 @@ extension Defaults.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)
```
*/
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 {
return value
} else if let serialized = Self.bridge.serialize(value as? Self.Value) {

View File

@ -2,7 +2,7 @@ import Defaults
import Foundation
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 {
var id: Int64
@ -25,7 +25,7 @@ private struct TimeZone: Hashable {
}
extension TimeZone: Defaults.NativeType {
/// Associated `CodableForm` to `CodableTimeZone`
/// Associated `CodableForm` to `CodableTimeZone`.
typealias CodableForm = CodableTimeZone
static let bridge = TimeZoneBridge()
@ -37,7 +37,7 @@ private struct CodableTimeZone {
}
extension CodableTimeZone: Defaults.CodableType {
/// Convert from `Codable` to `Native`
/// Convert from `Codable` to `Native`.
func toNative() -> TimeZone {
TimeZone(id: id, name: name)
}

View File

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

View File

@ -1,76 +1,68 @@
# Migration Guide From v4 to v5
# Migration guide from v4 to v5
## Warning
If the migration is not success or incomplete. Edit `Defaults.Key` might cause data loss.
**Please back up your UserDefaults data before migration.**
**Warning: Test the migration thoroughly in your app. It might cause unintended data loss if you're not careful.**
## Summary
Before v4, `Defaults` store `Codable` types as a JSON string.
After v5, `Defaults` store `Defaults.Serializable` types with `UserDefaults` native supported type.
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.
- 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
// Before
// v4
let key = Defaults.Key<[String: Int]>("key", default: ["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]
UserDefaults.standard.string(forKey: "key")
//=> "[\"0\": 0]"
```
All types should conform to `Defaults.Serializable` in order to work with `Defaults`.
So this will require some migrations to resolve **TWO** major issues.
```swift
// v5
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.dictionary(forKey: "key")
//=> [0: 0]
```
## Issues
1. **Compiler complain that `Defaults.Key<Value>` is not conform to `Defaults.Serializable`.**
Since we replace `Codable` with `Defaults.Serializable`, `Key<Value>` will have to conform to `Value: Defaults.Serializable`.
For this situation, please follow the guide below:
1. **The compiler complains that `Defaults.Key<Value>` does not conform to `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 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` 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`).**
In v5, `Defaults` reads value from `UserDefaults` as a native supported type.
But `UserDefaults` only contains JSON string before migration, `Defaults` will not be able to work with it.
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 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)
2. **The previous value in `UserDefaults` is not readable. (for example, `Defaults[.array]` returns `nil`).**
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.
- [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)
- [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)
## Testing
We recommend user doing some tests after migration.
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.
We recommend doing some manual testing after migrating.
Here is the guide for making a migration test:
For example you are trying to migrate a `Codable String` array to native array.
For example, let's say you are trying to migrate an array of `Codable` string to a 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
let string = "[\"a\",\"b\",\"c\"]"
```
2. Insert the value above into UserDefaults.
2. Insert the above value into `UserDefaults`.
```swift
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
let key = Defaults.Key<[String]>("testKey", default: [])
@ -79,15 +71,15 @@ Defaults.migrate(key, to: .v5)
Defaults[key] //=> [a, b, c]
```
---
## Migrations
### 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
private struct TimeZone: Codable {
@ -102,10 +94,10 @@ extension Defaults.Keys {
#### Migration steps
1. Let `TimeZone` conform to `Defaults.Serializable`.
1. Make `TimeZone` conform to `Defaults.Serializable`.
```swift
private struct TimeZone: Defaults.Serializable, Codable {
private struct TimeZone: Codable, Defaults.Serializable {
var id: String
var name: String
}
@ -113,15 +105,13 @@ private struct TimeZone: Defaults.Serializable, Codable {
2. Now `Defaults[.timezone]` should be readable.
---
### 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
private enum Period: String, Codable {
@ -137,7 +127,7 @@ extension Defaults.Keys {
#### Migration steps
1. Let `Period` conform to `Defaults.Serializable`.
1. Make `Period` conform to `Defaults.Serializable`.
```swift
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.
---
### 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, your code should be like this
#### Before migration
```swift
extension Defaults.Keys {
@ -170,25 +158,24 @@ extension Defaults.Keys {
#### 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.
---
### 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, your code should be like this
#### Before migration
```swift
private struct TimeZone: Codable, Hashable {
private struct TimeZone: Hashable, Codable {
var id: String
var name: String
}
private enum Period: String, Codable, Hashable {
private enum Period: String, Hashable, Codable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
@ -206,33 +193,35 @@ extension Defaults.Keys {
#### Migration steps
1. Let `TimeZone` and `Period` conform to `Defaults.Serializable`
1. Make `TimeZone` and `Period` conform to `Defaults.Serializable`.
```swift
private struct TimeZone: Defaults.Serializable, Codable, Hashable {
private struct TimeZone: Hashable, Codable, Defaults.Serializable {
var id: 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 halfHour = "30 Minutes"
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.
---
### 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
private enum Period: String, Codable {
@ -248,7 +237,7 @@ extension Defaults.Keys {
#### 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
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
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
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.
* hints: You can also implement `toNative` function at `Defaults.CodableType` in your own way.
For example
You can also instead implement the `toNative` function in `Defaults.CodableType` for flexibility:
```swift
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)
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
#### Before migration
```swift
private struct TimeZone: Codable {
@ -329,7 +313,7 @@ extension Defaults.Keys {
#### 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
private struct TimeZoneBridge: Defaults.Bridge {
@ -341,7 +325,10 @@ private struct TimeZoneBridge: Defaults.Bridge {
return nil
}
return ["id": value.id, "name": value.name]
return [
"id": value.id,
"name": value.name
]
}
func deserialize(_ object: Serializable?) -> TimeZone? {
@ -353,12 +340,15 @@ private struct TimeZoneBridge: Defaults.Bridge {
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
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
private struct CodableTimeZone {
@ -380,7 +370,7 @@ private struct CodableTimeZone {
}
extension CodableTimeZone: Defaults.CodableType {
/// Convert from `Codable` to `Native`
/// Convert from `Codable` to native type.
func toNative() -> TimeZone {
TimeZone(id: id, name: name)
}
@ -391,14 +381,13 @@ extension CodableTimeZone: Defaults.CodableType {
```swift
extension TimeZone: Defaults.NativeType {
/// Associated `CodableForm` to `CodableTimeZone`
typealias CodableForm = CodableTimeZone
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.
**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.**
**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.**

491
readme.md
View File

@ -52,26 +52,25 @@ pod 'Defaults'
## Support types
| Single Value |
|:------------------:|
| `Int(8/16/32/64)` |
| `UInt(8/16/32/64)` |
| `Double` |
| `Float` |
| `String` |
| `CGFloat` |
| `Bool` |
| `Date` |
| `Data` |
| `URL` |
| `NSColor` (macOS) |
| `UIColor` (iOS) |
| `Codable` |
- `Int(8/16/32/64)`
- `UInt(8/16/32/64)`
- `Double`
- `CGFloat`
- `Float`
- `String`
- `Bool`
- `Date`
- `Data`
- `URL`
- `NSColor` (macOS)
- `UIColor` (iOS)
- `Codable`
The list above only show the type that does not need further more configuration.
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 examples, see [Tests/DefaultsTests](./Tests/DefaultsTests).
Defaults also support the above types wrapped in `Array`, `Set`, `Dictionary`, and even wrapped in nested types. For example, `[[String: Set<[String: Int]>]]`.
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).
You can easily add support for any custom type.
## Usage
@ -137,7 +136,9 @@ Defaults[.defaultDuration].rawValue
//=> "1 Hour"
```
### Codable Example
*(This works as long as the raw value of the enum is any of the supported types)*
### Codable example
```swift
struct User: Codable, Defaults.Serializable {
@ -339,217 +340,6 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
//=> 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
### `Defaults`
@ -586,9 +376,9 @@ public protocol DefaultsSerializable {
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`
@ -596,6 +386,7 @@ It should have a static variable `bridge` which protocol should conform to `Defa
public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value?
}
@ -603,17 +394,14 @@ public protocol DefaultsBridge {
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.
`Serializable` is the type stored in `UserDefaults`.
`serialize` will be executed before storing to the `UserDefaults` .
`deserialize` will be executed after retrieving its value from the `UserDefaults`.
- `Value`: The type you want to use.
- `Serializable`: The type stored in `UserDefaults`.
- `serialize`: Executed before storing to the `UserDefaults` .
- `deserialize`: Executed after retrieving its value from the `UserDefaults`.
#### `Defaults.reset(keys…)`
@ -621,8 +409,6 @@ Type: `func`
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.
#### `Defaults.observe`
@ -740,11 +526,9 @@ Type: `func`
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:)`
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
@ -776,15 +560,222 @@ A `SetAlgebra` which can store into the native `UserDefaults`.
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
### How can I store a dictionary of arbitrary values?
After `Defaults` v5, you don't need to use `Codable` to store dictionary, `Defaults` supports storing dictionary natively.
After `Defaults` v5, you don't need to use `Codable` to store dictionary, `Defaults` supports storing dictionary natively.
For `Defaults` support types, see [Support types](#support-types).
There might be situations where you want to use `[String: Any]` directly.
Unfortunately, since `Any` can not conform to `Defaults.Serializable`, `Defaults` can not support it.
There might be situations where you want to use `[String: Any]` directly.
Unfortunately, since `Any` can not conform to `Defaults.Serializable`, `Defaults` can not support it.
However, you can use the [`AnyCodable`](https://github.com/Flight-School/AnyCodable) package to work around this `Defaults.Serializable` limitation: