diff --git a/Sources/Defaults/Defaults+AnySerializable.swift b/Sources/Defaults/Defaults+AnySerializable.swift index 55b35de..851feb6 100644 --- a/Sources/Defaults/Defaults+AnySerializable.swift +++ b/Sources/Defaults/Defaults+AnySerializable.swift @@ -11,7 +11,7 @@ extension Defaults { `get` will deserialize the internal value to the type that user specify in the function parameter. - ``` + ```swift let any = Defaults.Key("independentAnyKey", default: 121_314) print(Defaults[any].get(Int.self)) @@ -20,7 +20,7 @@ extension Defaults { - Note: The only way to assign a non-serializable value is using `ExpressibleByArrayLiteral` or `ExpressibleByDictionaryLiteral` to assign a type that is not a `UserDefaults` natively supported type. - ``` + ```swift private enum mime: String, Defaults.Serializable { case JSON = "application/json" } diff --git a/Sources/Defaults/Defaults+Protocol.swift b/Sources/Defaults/Defaults+Protocol.swift index dbee8b0..96db946 100644 --- a/Sources/Defaults/Defaults+Protocol.swift +++ b/Sources/Defaults/Defaults+Protocol.swift @@ -1,25 +1,9 @@ import Foundation -/** -Types that conform to this protocol can be used with `Defaults`. - -The type should have a static variable `bridge` which should reference an instance of a type that conforms to `Defaults.Bridge`. - -``` -struct User { - username: String - password: String -} - -extension User: Defaults.Serializable { - static let bridge = UserBridge() -} -``` -*/ -public protocol DefaultsSerializable { +public protocol _DefaultsSerializable { typealias Value = Bridge.Value typealias Serializable = Bridge.Serializable - associatedtype Bridge: DefaultsBridge + associatedtype Bridge: Defaults.Bridge /** Static bridge for the `Value` which cannot be stored natively. @@ -32,59 +16,7 @@ public protocol DefaultsSerializable { static var isNativelySupportedType: Bool { get } } -/** -A `Bridge` is responsible for serialization and deserialization. - -It has two associated types `Value` and `Serializable`. - -- `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 { - username: String - password: String -} - -extension User { - static let bridge = UserBridge() -} - -struct UserBridge: Defaults.Bridge { - typealias Value = User - typealias Serializable = [String: String] - - func serialize(_ value: Value?) -> Serializable? { - guard let value else { - return nil - } - - return [ - "username": value.username, - "password": value.password - ] - } - - func deserialize(_ object: Serializable?) -> Value? { - guard - let object, - let username = object["username"], - let password = object["password"] - else { - return nil - } - - return User( - username: username, - password: password - ) - } -} -``` -*/ -public protocol DefaultsBridge { +public protocol _DefaultsBridge { associatedtype Value associatedtype Serializable @@ -92,45 +24,27 @@ public protocol DefaultsBridge { func deserialize(_ object: Serializable?) -> Value? } -public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable { +public protocol _DefaultsCollectionSerializable: Collection, Defaults.Serializable { /** `Collection` does not have a initializer, but we need a initializer to convert an array into the `Value`. */ init(_ elements: [Element]) } -public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable { +public protocol _DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable { /** Since `SetAlgebra` protocol does not conform to `Sequence`, we cannot convert a `SetAlgebra` to an `Array` directly. */ func toArray() -> [Element] } -/** -Convenience protocol for `Codable`. -*/ -public protocol DefaultsCodableBridge: Defaults.Bridge where Serializable == String, Value: Codable {} +public protocol _DefaultsCodableBridge: Defaults.Bridge where Serializable == String, Value: Codable {} -/** -Ambiguous bridge selector protocol. This lets you select your preferred bridge when there are multiple possibilities. - -For example: - -``` -enum Interval: Int, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable { - case tenMinutes = 10 - case halfHour = 30 - case oneHour = 60 -} -``` - -By default, if an `enum` conforms to `Codable` and `Defaults.Serializable`, it will use the `CodableBridge`, but by conforming to `Defaults.PreferRawRepresentable`, we can switch the bridge back to `RawRepresentableBridge`. -*/ -public protocol DefaultsPreferRawRepresentable: RawRepresentable {} -public protocol DefaultsPreferNSSecureCoding: NSSecureCoding {} +public protocol _DefaultsPreferRawRepresentable: RawRepresentable {} +public protocol _DefaultsPreferNSSecureCoding: NSSecureCoding {} // Essential properties for serializing and deserializing `ClosedRange` and `Range`. -public protocol DefaultsRange { +public protocol _DefaultsRange { associatedtype Bound: Comparable, Defaults.Serializable var lowerBound: Bound { get } diff --git a/Sources/Defaults/Defaults.swift b/Sources/Defaults/Defaults.swift index 88c2328..6981ebf 100644 --- a/Sources/Defaults/Defaults.swift +++ b/Sources/Defaults/Defaults.swift @@ -2,18 +2,45 @@ import Foundation public enum Defaults { - public typealias Keys = AnyKey - public typealias Serializable = DefaultsSerializable - public typealias CollectionSerializable = DefaultsCollectionSerializable - public typealias SetAlgebraSerializable = DefaultsSetAlgebraSerializable - public typealias PreferRawRepresentable = DefaultsPreferRawRepresentable - public typealias PreferNSSecureCoding = DefaultsPreferNSSecureCoding - public typealias Bridge = DefaultsBridge - public typealias RangeSerializable = DefaultsRange & DefaultsSerializable - typealias CodableBridge = DefaultsCodableBridge + /** + Access stored values. + ```swift + import Defaults + + extension Defaults.Keys { + static let quality = Key("quality", default: 0.8) + } + + // … + + Defaults[.quality] + //=> 0.8 + + Defaults[.quality] = 0.5 + //=> 0.5 + + Defaults[.quality] += 0.1 + //=> 0.6 + + Defaults[.quality] = "🦄" + //=> [Cannot assign value of type 'String' to type 'Double'] + ``` + */ + public static subscript(key: Key) -> Value { + get { key.suite[key] } + set { + key.suite[key] = newValue + } + } +} + +extension Defaults { // We cannot use `Key` as the container for keys because of "Static stored properties not supported in generic types". - public class AnyKey { + /** + Type-erased key. + */ + public class _AnyKey { public typealias Key = Defaults.Key public let name: String @@ -31,8 +58,25 @@ public enum Defaults { suite.removeObject(forKey: name) } } +} - public final class Key: AnyKey { +extension Defaults { + /** + Strongly-typed key used to access values. + + You declare the defaults keys upfront with a type and default value. + + ```swift + import Defaults + + extension Defaults.Keys { + static let quality = Key("quality", default: 0.8) + // ^ ^ ^ ^ + // Key Type UserDefaults name Default value + } + ``` + */ + public final class Key: _AnyKey { /** It will be executed in these situations: @@ -50,7 +94,11 @@ public enum Defaults { The `default` parameter should not be used if the `Value` type is an optional. */ - public init(_ key: String, default defaultValue: Value, suite: UserDefaults = .standard) { + public init( + _ key: String, + default defaultValue: Value, + suite: UserDefaults = .standard + ) { self.defaultValueGetter = { defaultValue } super.init(name: key, suite: suite) @@ -78,19 +126,30 @@ public enum Defaults { } ``` + - Parameter key: The key must be ASCII, not start with `@`, and cannot contain a dot (`.`). + - Note: This initializer will not set the default value in the actual `UserDefaults`. This should not matter much though. It's only really useful if you use legacy KVO bindings. */ - public init(_ key: String, suite: UserDefaults = .standard, default defaultValueGetter: @escaping () -> Value) { + public init( + _ key: String, + suite: UserDefaults = .standard, + default defaultValueGetter: @escaping () -> Value + ) { self.defaultValueGetter = defaultValueGetter super.init(name: key, suite: suite) } - } - public static subscript(key: Key) -> Value { - get { key.suite[key] } - set { - key.suite[key] = newValue + /** + Create a defaults key with an optional value. + + - Parameter key: The key must be ASCII, not start with `@`, and cannot contain a dot (`.`). + */ + public convenience init( + _ key: String, + suite: UserDefaults = .standard + ) where Value == T? { + self.init(key, default: nil, suite: suite) } } } @@ -106,27 +165,122 @@ extension Defaults { } } -extension Defaults.Key { - /** - Create a defaults key. - - - Parameter key: The key must be ASCII, not start with `@`, and cannot contain a dot (`.`). - */ - public convenience init(_ key: String, suite: UserDefaults = .standard) where Value == T? { - self.init(key, default: nil, suite: suite) - } -} - -extension Defaults.AnyKey: Equatable { - public static func == (lhs: Defaults.AnyKey, rhs: Defaults.AnyKey) -> Bool { +extension Defaults._AnyKey: Equatable { + public static func == (lhs: Defaults._AnyKey, rhs: Defaults._AnyKey) -> Bool { lhs.name == rhs.name && lhs.suite == rhs.suite } } -extension Defaults.AnyKey: Hashable { +extension Defaults._AnyKey: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(name) hasher.combine(suite) } } + +extension Defaults { + public typealias Keys = _AnyKey + + /** + Types that conform to this protocol can be used with `Defaults`. + + The type should have a static variable `bridge` which should reference an instance of a type that conforms to `Defaults.Bridge`. + + ```swift + struct User { + username: String + password: String + } + + extension User: Defaults.Serializable { + static let bridge = UserBridge() + } + ``` + */ + public typealias Serializable = _DefaultsSerializable + + public typealias CollectionSerializable = _DefaultsCollectionSerializable + public typealias SetAlgebraSerializable = _DefaultsSetAlgebraSerializable + + /** + Ambiguous bridge selector protocol that lets you select your preferred bridge when there are multiple possibilities. + + ```swift + enum Interval: Int, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable { + case tenMinutes = 10 + case halfHour = 30 + case oneHour = 60 + } + ``` + + By default, if an `enum` conforms to `Codable` and `Defaults.Serializable`, it will use the `CodableBridge`, but by conforming to `Defaults.PreferRawRepresentable`, we can switch the bridge back to `RawRepresentableBridge`. + */ + public typealias PreferRawRepresentable = _DefaultsPreferRawRepresentable + + /** + Ambiguous bridge selector protocol that lets you select your preferred bridge when there are multiple possibilities. + */ + public typealias PreferNSSecureCoding = _DefaultsPreferNSSecureCoding + + /** + A `Bridge` is responsible for serialization and deserialization. + + It has two associated types `Value` and `Serializable`. + + - `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`. + + ```swift + struct User { + username: String + password: String + } + + extension User { + static let bridge = UserBridge() + } + + struct UserBridge: Defaults.Bridge { + typealias Value = User + typealias Serializable = [String: String] + + func serialize(_ value: Value?) -> Serializable? { + guard let value else { + return nil + } + + return [ + "username": value.username, + "password": value.password + ] + } + + func deserialize(_ object: Serializable?) -> Value? { + guard + let object, + let username = object["username"], + let password = object["password"] + else { + return nil + } + + return User( + username: username, + password: password + ) + } + } + ``` + */ + public typealias Bridge = _DefaultsBridge + + public typealias RangeSerializable = _DefaultsRange & _DefaultsSerializable + + /** + Convenience protocol for `Codable`. + */ + typealias CodableBridge = _DefaultsCodableBridge +} diff --git a/Sources/Defaults/Documentation.docc/Documentation.md b/Sources/Defaults/Documentation.docc/Documentation.md new file mode 100644 index 0000000..8f8d802 --- /dev/null +++ b/Sources/Defaults/Documentation.docc/Documentation.md @@ -0,0 +1,60 @@ +# ``Defaults`` + +Store key-value pairs persistently across launches of your app. + +It uses [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) underneath but exposes a type-safe facade with lots of nice conveniences. + +## Usage + +You declare the defaults keys upfront with a type and default value. + +```swift +import Defaults + +extension Defaults.Keys { + static let quality = Key("quality", default: 0.8) + // ^ ^ ^ ^ + // Key Type UserDefaults name Default value +} +``` + +You can then access it as a subscript on the `Defaults` global: + +```swift +Defaults[.quality] +//=> 0.8 + +Defaults[.quality] = 0.5 +//=> 0.5 +``` + +[Learn More](https://github.com/sindresorhus/Defaults#usage) + +## Topics + +### Essentials + +- ``Defaults/subscript(_:)`` +- ``Defaults/Key`` +- ``Defaults/Serializable`` + +### Methods + +- ``Defaults/reset(_:)-7jv5v`` +- ``Defaults/reset(_:)-7es1e`` +- ``Defaults/removeAll(suite:)`` + +### SwiftUI + +- ``Default`` +- ``Defaults/Toggle`` + +### Events + +- ``Defaults/publisher(_:options:)`` +- ``Defaults/publisher(keys:options:)`` + +### Force Type Resolution + +- ``Defaults/PreferRawRepresentable`` +- ``Defaults/PreferNSSecureCoding`` diff --git a/Sources/Defaults/Migration/Migration+Defaults.swift b/Sources/Defaults/Migration/Migration+Defaults.swift index 0a720cb..d87ebdb 100644 --- a/Sources/Defaults/Migration/Migration+Defaults.swift +++ b/Sources/Defaults/Migration/Migration+Defaults.swift @@ -8,7 +8,7 @@ extension Defaults { /** Migrate the given key's value from JSON string to `Value`. - ``` + ```swift extension Defaults.Keys { static let array = Key?>("array") } diff --git a/Sources/Defaults/Migration/v5/Migration+Extensions.swift b/Sources/Defaults/Migration/v5/Migration+Extensions.swift index f270f1e..d9243d4 100644 --- a/Sources/Defaults/Migration/v5/Migration+Extensions.swift +++ b/Sources/Defaults/Migration/v5/Migration+Extensions.swift @@ -2,8 +2,8 @@ import Foundation import CoreGraphics extension Defaults { - public typealias NativeType = DefaultsNativeType - public typealias CodableType = DefaultsCodableType + public typealias NativeType = _DefaultsNativeType + public typealias CodableType = _DefaultsCodableType } extension Data: Defaults.NativeType { diff --git a/Sources/Defaults/Migration/v5/Migration+Protocol.swift b/Sources/Defaults/Migration/v5/Migration+Protocol.swift index 6acf0c9..a68c422 100644 --- a/Sources/Defaults/Migration/v5/Migration+Protocol.swift +++ b/Sources/Defaults/Migration/v5/Migration+Protocol.swift @@ -9,7 +9,7 @@ It should have an associated type name `CodableForm` where its protocol conform So we can convert the JSON string into a `NativeType` like this: -``` +```swift guard let jsonData = string?.data(using: .utf8), let codable = try? JSONDecoder().decode(NativeType.CodableForm.self, from: jsonData) @@ -20,7 +20,7 @@ else { return codable.toNative() ``` */ -public protocol DefaultsNativeType: Defaults.Serializable { +public protocol _DefaultsNativeType: Defaults.Serializable { associatedtype CodableForm: Defaults.CodableType } @@ -31,7 +31,7 @@ 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`. nd it also have a `toNative()` function to convert itself into `NativeForm`. -``` +```swift struct User { username: String password: String @@ -55,7 +55,7 @@ extension CodableUser: Defaults.CodableType { } ``` */ -public protocol DefaultsCodableType: Codable { +public protocol _DefaultsCodableType: Codable { associatedtype NativeForm: Defaults.NativeType func toNative() -> NativeForm } diff --git a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift b/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift index 8738724..feb7c2b 100644 --- a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift +++ b/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift @@ -19,19 +19,19 @@ extension UserDefaults { 1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`. - ``` + ```swift JSONDecoder().decode([String].CodableForm.self, from: jsonData) ``` 2. If `Array` conforms to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`. - ``` + ```swift JSONDecoder().decode([String.CodableForm].self, from: jsonData) ``` 3. `String`'s `CodableForm` is `self`, because `String` is `Codable`. - ``` + ```swift JSONDecoder().decode([String].self, from: jsonData) ``` */ diff --git a/Sources/Defaults/Observation+Combine.swift b/Sources/Defaults/Observation+Combine.swift index 7414a12..26af62c 100644 --- a/Sources/Defaults/Observation+Combine.swift +++ b/Sources/Defaults/Observation+Combine.swift @@ -74,7 +74,7 @@ extension Defaults { /** Returns a type-erased `Publisher` that publishes changes related to the given key. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -103,7 +103,7 @@ extension Defaults { */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *) public static func publisher( - keys: AnyKey..., + keys: _AnyKey..., options: ObservationOptions = [.initial] ) -> AnyPublisher { let initial = Empty(completeImmediately: false).eraseToAnyPublisher() diff --git a/Sources/Defaults/Observation.swift b/Sources/Defaults/Observation.swift index 9f85a9d..c3a7db6 100644 --- a/Sources/Defaults/Observation.swift +++ b/Sources/Defaults/Observation.swift @@ -1,12 +1,12 @@ import Foundation -public protocol DefaultsObservation: AnyObject { +public protocol _DefaultsObservation: AnyObject { func invalidate() /** Keep this observation alive for as long as, and no longer than, another object exists. - ``` + ```swift Defaults.observe(.xyz) { [unowned self] change in self.xyz = change.newValue }.tieToLifetime(of: self) @@ -25,7 +25,7 @@ public protocol DefaultsObservation: AnyObject { } extension Defaults { - public typealias Observation = DefaultsObservation + public typealias Observation = _DefaultsObservation public enum ObservationOption { /** @@ -95,7 +95,7 @@ extension Defaults { - Note: This only works with `Defaults.observe()` and `Defaults.publisher()`. User-made KVO will not be affected. - ``` + ```swift let observer = Defaults.observe(keys: .key1, .key2) { // … @@ -283,7 +283,7 @@ extension Defaults { /** Observe a defaults key. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -312,7 +312,7 @@ extension Defaults { /** Observe multiple keys of any type, but without any information about the changes. - ``` + ```swift extension Defaults.Keys { static let setting1 = Key("setting1", default: false) static let setting2 = Key("setting2", default: true) @@ -324,7 +324,7 @@ extension Defaults { ``` */ public static func observe( - keys: AnyKey..., + keys: _AnyKey..., options: ObservationOptions = [.initial], handler: @escaping () -> Void ) -> Observation { diff --git a/Sources/Defaults/Reset.swift b/Sources/Defaults/Reset.swift index d86ddcd..fbf1970 100644 --- a/Sources/Defaults/Reset.swift +++ b/Sources/Defaults/Reset.swift @@ -9,7 +9,7 @@ extension Defaults { - Parameter keys: String keys to reset. - Parameter suite: `UserDefaults` suite. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -36,7 +36,7 @@ extension Defaults { - Parameter keys: String keys to reset. - Parameter suite: `UserDefaults` suite. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -62,7 +62,7 @@ extension Defaults { /** Reset the given keys back to their default values. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -76,14 +76,14 @@ extension Defaults { //=> false ``` */ - public static func reset(_ keys: AnyKey...) { + public static func reset(_ keys: _AnyKey...) { reset(keys) } /** Reset the given keys back to their default values. - ``` + ```swift extension Defaults.Keys { static let isUnicornMode = Key("isUnicornMode", default: false) } @@ -97,7 +97,7 @@ extension Defaults { //=> false ``` */ - public static func reset(_ keys: [AnyKey]) { + public static func reset(_ keys: [_AnyKey]) { for key in keys { key.reset() } diff --git a/Sources/Defaults/SwiftUI.swift b/Sources/Defaults/SwiftUI.swift index a85f2a3..f93f2fa 100644 --- a/Sources/Defaults/SwiftUI.swift +++ b/Sources/Defaults/SwiftUI.swift @@ -62,6 +62,11 @@ extension Defaults { } } +/** +Access stored values from SwiftUI. + +This is similar to `@AppStorage` but it accepts a ``Defaults/Key`` and many more types. +*/ @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct Default: DynamicProperty { @@ -77,7 +82,7 @@ public struct Default: DynamicProperty { - Important: You cannot use this in an `ObservableObject`. It's meant to be used in a `View`. - ``` + ```swift extension Defaults.Keys { static let hasUnicorn = Key("hasUnicorn", default: false) } @@ -126,7 +131,7 @@ public struct Default: DynamicProperty { /** Reset the key back to its default value. - ``` + ```swift extension Defaults.Keys { static let opacity = Key("opacity", default: 1) } @@ -158,11 +163,11 @@ extension Default where Value: Equatable { @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) extension Defaults { /** - Creates a SwiftUI `Toggle` view that is connected to a `Defaults` key with a `Bool` value. + A SwiftUI `Toggle` view that is connected to a ``Defaults/Key`` with a `Bool` value. The toggle works exactly like the SwiftUI `Toggle`. - ``` + ```swift extension Defaults.Keys { static let showAllDayEvents = Key("showAllDayEvents", default: false) } @@ -176,7 +181,7 @@ extension Defaults { You can also listen to changes: - ``` + ```swift struct ShowAllDayEventsSetting: View { var body: some View { Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents) diff --git a/Sources/Defaults/Utilities.swift b/Sources/Defaults/Utilities.swift index 682bc6f..bfc6536 100644 --- a/Sources/Defaults/Utilities.swift +++ b/Sources/Defaults/Utilities.swift @@ -59,7 +59,7 @@ final class LifetimeAssociation { When either the owner or the new `LifetimeAssociation` is destroyed, the given deinit handler, if any, is called. - ``` + ```swift class Ghost { var association: LifetimeAssociation? @@ -159,7 +159,7 @@ extension Defaults.Serializable { Converts a natively supported type from `UserDefaults` into `Self`. - ``` + ```swift guard let anyObject = object(forKey: key) else { return nil } @@ -188,7 +188,7 @@ extension Defaults.Serializable { Converts `Self` into `UserDefaults` native support type. - ``` + ```swift set(Value.toSerialize(value), forKey: key) ``` */ diff --git a/readme.md b/readme.md index 6bd48f9..d4d12b7 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ Store key-value pairs persistently across launches of your app. -It uses `NSUserDefaults` underneath but exposes a type-safe facade with lots of nice conveniences. +It uses `UserDefaults` underneath but exposes a type-safe facade with lots of nice conveniences. It's used in production by apps like [Gifski](https://github.com/sindresorhus/Gifski), [Dato](https://sindresorhus.com/dato), [Lungo](https://sindresorhus.com/lungo), [Battery Indicator](https://sindresorhus.com/battery-indicator), and [HEIC Converter](https://sindresorhus.com/heic-converter). @@ -78,12 +78,13 @@ If a type conforms to both `NSSecureCoding` and `Codable`, then `Codable` will b ## Usage -You declare the defaults keys upfront with type and default value. +[API documentation.](https://swiftpackageindex.com/sindresorhus/Defaults/documentation/defaults) + +You declare the defaults keys upfront with a type and default value. **The key name must be ASCII, not start with `@`, and cannot contain a dot (`.`).** ```swift -import Cocoa import Defaults extension Defaults.Keys {