Defaults

repository·main·Indexed 25 days ago

https://github.com/sindresorhus/defaults

A type-safe, modern wrapper for UserDefaults that provides a Swifty interface for persistent key-value storage. It supports Codable types, NSSecureCoding, iCloud synchronization, and SwiftUI integration via the @Default property wrapper. It allows for strongly-typed keys, observation of changes, and custom serialization through Bridges.

Tokens
6.4K
Snippets
26
Records
37
Agent score
81%

What's inside Defaults

  1. Overview of Defaults

    main

    Defaults is a type-safe, modern wrapper around Apple's UserDefaults. It provides a facade that allows you to store key-value pairs persistently across app launches with several advanced features:

    • Strongly typed: Declare types and default values upfront.
    • Codable & NSSecureCoding support: Store complex types like enums or custom structs.
    • SwiftUI Integration: Use property wrappers for reactive UI updates.
    • Observation: Monitor changes to specific keys.
    • iCloud Support: Automatically synchronize data across devices.
    • Debuggable: Data is stored as JSON-serialized values.
  2. Override default serialization preference

    main

    By default, if a type conforms to multiple serialization protocols (like Codable and RawRepresentable), Defaults will automatically prefer Codable.

    You can override this behavior to use a different bridge by conforming to one of these protocols:

    • Defaults.PreferRawRepresentable: Forces the use of the raw value (useful for enums to avoid JSON overhead).
    • Defaults.PreferNSSecureCoding: Forces the use of NSSecureCoding (useful for Objective-C classes like NSColor).

    Warning: Changing the preferred bridge for a key already in production can break existing stored values. Use migrations when changing bridge preferences.

    enum Mime: String, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable {
    	case json = "application/json"
    }
    
    extension Defaults.Keys {
    	static let mime = Key<Mime>("mime", default: .json)
    }
    
    // Result: UserDefaults stores "application/json" as a string, not a JSON-encoded string.
  3. Persist custom collection types

    main

    You can make custom containers (like bespoke Bags or Sets) persistable by conforming to specific protocols:

    • For Collection types: Conform to Defaults.CollectionSerializable. Defaults needs a way to turn the collection into an array and back again.
    • For Set algebra types: Conform to Defaults.SetAlgebraSerializable. You must implement toArray() -> [Element] to facilitate serialization.
    struct Bag<Element: Defaults.Serializable>: Collection {
    	var items: [Element]
    	// ... implement Collection requirements ...
    }
    
    extension Bag: Defaults.CollectionSerializable {
    	init(_ elements: [Element]) {
    		self.items = elements
    	}
    }
    
    // Usage
    extension Defaults.Keys {
    	static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"]))
    }
  4. Declare and use type-safe keys

    main

    To use Defaults, extend Defaults.Keys to define your keys. Each key is a Key<T> where T is the type of the value and the second parameter is the default value. You can then access and modify these values using the Defaults subscript.

    import Defaults
    
    extension Defaults.Keys {
    	static let quality = Key<Double>("quality", default: 0.8)
    }
    
    // Accessing values
    Defaults[.quality]
    //=> 0.8
    
    // Setting values
    Defaults[.quality] = 0.5
    //=> 0.5
  5. Define and use strongly-typed keys

    main

    To ensure type safety and centralize your configuration, extend Defaults.Keys with static properties using the Key<T> type. This allows you to define the key name and its default value in one place. You can then access or modify these values using the Defaults subscript with the key you defined.

    import Defaults
    
    extension Defaults.Keys {
    	static let quality = Key<Double>("quality", default: 0.8)
    }
    
    // Reading a value
    Defaults[.quality]
    //=> 0.8
    
    // Writing a value
    Defaults[.quality] = 0.5
    //=> 0.5
  6. Migrate from @AppStorage to Defaults

    main

    To migrate from SwiftUI's @AppStorage to Defaults, centralize your keys in an extension of Defaults.Keys and replace the @AppStorage property wrapper with @Default. This provides better type safety and centralized management.

    // Before (with @AppStorage):
    struct SettingsView: View {
    	@AppStorage("showPreview") var showPreview = true
    
    	var body: some View {
    		Toggle("Show Preview", isOn: $showPreview)
    	}
    }
    
    // After (with Defaults):
    extension Defaults.Keys {
    	static let showPreview = Key<Bool>("showPreview", default: true)
    }
    
    struct SettingsView: View {
    	@Default(.showPreview) var showPreview
    
    	var body: some View {
    		Toggle("Show Preview", isOn: $showPreview)
    	}
    }
  7. Declare persistent keys with Defaults.Key

    main

    To use Defaults, you must first declare keys. The recommended pattern is to extend Defaults.Keys with static properties of type Key<T>.

    Key Naming Rules:

    • Must be ASCII.
    • Cannot start with @.
    • Cannot contain a dot (.).

    You can declare keys with a fixed default value, an optional type (which defaults to nil), or a dynamic default value using a closure.

    import Defaults
    
    // Fixed default value
    extension Defaults.Keys {
    	static let quality = Key<Double>("quality", default: 0.8)
    }
    
    // Optional key (defaults to nil)
    extension Defaults.Keys {
    	static let name = Key<String?>("name")
    }
    
    // Dynamic default value
    extension Defaults.Keys {
    	static let camera = Key<AVCaptureDevice?>("camera") { .default(for: .video) }
    }
    
    // Using keys directly without extension
    let isUnicorn = Defaults.Key<Bool>("isUnicorn", default: true)
  8. Install Defaults via Swift Package Manager

    main

    To add Defaults to your Xcode project, use the Swift Package Manager by adding the following URL in the "Swift Package Manager" tab in Xcode:

    https://github.com/sindresorhus/Defaults

    https://github.com/sindresorhus/Defaults