diff --git a/Defaults.podspec b/Defaults.podspec index 4039e74..3a246d6 100644 --- a/Defaults.podspec +++ b/Defaults.podspec @@ -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' diff --git a/Package.swift b/Package.swift index 462b8ab..b88d71a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.4 import PackageDescription let package = Package( diff --git a/Sources/Defaults/Defaults+Bridge.swift b/Sources/Defaults/Defaults+Bridge.swift index a635a60..f0815ef 100644 --- a/Sources/Defaults/Defaults+Bridge.swift +++ b/Sources/Defaults/Defaults+Bridge.swift @@ -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: 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: 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)` 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)` 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: Defaults.Bridge { diff --git a/Sources/Defaults/Defaults+Extensions.swift b/Sources/Defaults/Defaults+Extensions.swift index 40c9f30..90fd54c 100644 --- a/Sources/Defaults/Defaults+Extensions.swift +++ b/Sources/Defaults/Defaults+Extensions.swift @@ -115,7 +115,6 @@ extension Set: Defaults.Serializable where Element: Defaults.Serializable { public static var bridge: Defaults.SetBridge { Defaults.SetBridge() } } - extension Array: Defaults.Serializable where Element: Defaults.Serializable { public static var isNativelySupportedType: Bool { Element.isNativelySupportedType } public static var bridge: Defaults.ArrayBridge { 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 diff --git a/Sources/Defaults/Defaults+Protocol.swift b/Sources/Defaults/Defaults+Protocol.swift index 9159697..5a4b865 100644 --- a/Sources/Defaults/Defaults+Protocol.swift +++ b/Sources/Defaults/Defaults+Protocol.swift @@ -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 {} diff --git a/Sources/Defaults/Migration/Migration+Defaults.swift b/Sources/Defaults/Migration/Migration+Defaults.swift index 1d463b8..fe1e58b 100644 --- a/Sources/Defaults/Migration/Migration+Defaults.swift +++ b/Sources/Defaults/Migration/Migration+Defaults.swift @@ -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 { diff --git a/Sources/Defaults/Migration/v5/Migration+Extensions.swift b/Sources/Defaults/Migration/v5/Migration+Extensions.swift index 0e68560..3aabb53 100644 --- a/Sources/Defaults/Migration/v5/Migration+Extensions.swift +++ b/Sources/Defaults/Migration/v5/Migration+Extensions.swift @@ -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 { diff --git a/Sources/Defaults/Migration/v5/Migration+Protocol.swift b/Sources/Defaults/Migration/v5/Migration+Protocol.swift index 0f9c891..9314338 100644 --- a/Sources/Defaults/Migration/v5/Migration+Protocol.swift +++ b/Sources/Defaults/Migration/v5/Migration+Protocol.swift @@ -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 { diff --git a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift b/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift index fc2050b..3d0b21a 100644 --- a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift +++ b/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift @@ -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(forKey key: String, of type: Value.Type) { guard diff --git a/Sources/Defaults/Utilities.swift b/Sources/Defaults/Utilities.swift index c1a684c..fb48134 100644 --- a/Sources/Defaults/Utilities.swift +++ b/Sources/Defaults/Utilities.swift @@ -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] 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) { diff --git a/Tests/DefaultsTests/DefaultsMigrationTests.swift b/Tests/DefaultsTests/DefaultsMigrationTests.swift index dffc7a1..508db9c 100644 --- a/Tests/DefaultsTests/DefaultsMigrationTests.swift +++ b/Tests/DefaultsTests/DefaultsMigrationTests.swift @@ -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) } diff --git a/Tests/DefaultsTests/DefaultsTests.swift b/Tests/DefaultsTests/DefaultsTests.swift index dce3a1a..c0eee51 100644 --- a/Tests/DefaultsTests/DefaultsTests.swift +++ b/Tests/DefaultsTests/DefaultsTests.swift @@ -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 { diff --git a/migration.md b/migration.md index 2e383f9..b788a14 100644 --- a/migration.md +++ b/migration.md @@ -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` is not conform to `Defaults.Serializable`.** - Since we replace `Codable` with `Defaults.Serializable`, `Key` will have to conform to `Value: Defaults.Serializable`. - For this situation, please follow the guide below: +1. **The compiler complains that `Defaults.Key` does not conform to `Defaults.Serializable`.** + Since we replaced `Codable` with `Defaults.Serializable`, `Key` 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.** \ No newline at end of file +**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.** diff --git a/readme.md b/readme.md index 61e90b4..9912343 100644 --- a/readme.md +++ b/readme.md @@ -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", default: User(name: "Hello", age: "24")) - static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")]) - static let setUser = Defaults.Key>("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: 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>("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: SetAlgebra { - var store = Set() - - init() {} - - init(_ store: Set) { - 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>("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", default: User(name: "Hello", age: "24")) + static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")]) + static let setUser = Defaults.Key>("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: 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>("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: SetAlgebra { + var store = Set() + + init() {} + + init(_ store: Set) { + 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>("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: