How Settings Are Managed and Persisted in vorssaint-utils: A Complete Technical Guide
vorssaint-utils persists all user preferences using macOS UserDefaults, accessed through type-safe keys defined in a central DefaultsKey enum and sanitized via a helper Defaults struct.
The vorssaint-utils macOS application implements a robust persistence layer for user-configurable preferences. By wrapping Apple's UserDefaults API with a consistent key-naming strategy and data sanitization utilities, the codebase ensures that settings remain consistent across sessions while remaining easy to maintain and test.
The UserDefaults Architecture in vorssaint-utils
The architecture isolates raw storage behind well-named keys and helper methods, ensuring that every component reads and writes preferences in a consistent, testable way. All settings are persisted automatically by the OS and restored on next launch, providing a seamless user experience.
Type-Safe Key Definitions with DefaultsKey
At the core of the system lies a central enum or struct named DefaultsKey that defines string constants for every preference option. This approach keeps the key space consistent and prevents string-typo bugs across the codebase.
// Example key definition
DefaultsKey.windowPreviewExcludedApps
DefaultsKey.windowGestureEnabled
DefaultsKey.radialMenuProfiles
Standard vs. Suite-Based Storage
Most components access preferences through UserDefaults.standard, the shared defaults object for the current user. However, the codebase also supports named suites via UserDefaults(suiteName: ...) for specific migration scenarios or sandboxed components, such as when interacting with com.apple.WindowManager.
The Settings Persistence Workflow
The repository follows a predictable five-step workflow for managing user preferences, from definition to UI observation.
Defining and Reading Preferences
UI components and services read values using the standard UserDefaults API, often providing fallback defaults when keys are missing. In Sources/Vorssaint/UI/Settings/WindowPreviewExclusionsList.swift, the application retrieves excluded applications using:
// Read a boolean preference
let gesturesEnabled = UserDefaults.standard.bool(forKey: DefaultsKey.windowGestureEnabled)
// Read an array with fallback to empty list
let excludedApps = UserDefaults.standard.stringArray(forKey: DefaultsKey.windowPreviewExcludedApps) ?? []
Data Sanitization
Before persisting or using raw UserDefaults data, the Defaults helper struct centralizes common logic to clean values. For bundle identifiers, Defaults.sanitizedBundleIdentifierList(...) removes empty strings and invalid entries, ensuring downstream components work with clean data:
let sanitized = Defaults.sanitizedBundleIdentifierList(bundleIDs)
Writing Values Back to Storage
Updates flow back through the standard API. In Sources/Vorssaint/UI/Settings/ShortcutsSettings.swift, sanitized data is persisted using:
func saveExcludedApps(_ bundleIDs: [String]) {
let sanitized = Defaults.sanitizedBundleIdentifierList(bundleIDs)
UserDefaults.standard.set(sanitized, forKey: DefaultsKey.windowPreviewExcludedApps)
}
Observing Changes in SwiftUI
Views refresh automatically using @State or @ObservedObject wrappers that initialize from saved values. When the underlying UserDefaults value changes, the UI updates to reflect the new state without requiring manual notification handling.
Handling Different Data Types
The implementation handles primitives, collections, and complex objects with specific patterns for each category.
Boolean Toggles
Simple on/off preferences use the native boolean API. The window-gesture service throughout the codebase checks flags using:
let enabled = UserDefaults.standard.bool(forKey: DefaultsKey.windowGestureEnabled)
Values are written using set(_:forKey:) with the appropriate DefaultsKey constant.
Collections and Property Lists
Array and dictionary values store as property-list-compatible collections. The window preview exclusion list manages arrays of bundle identifier strings:
// Retrieval
let apps = UserDefaults.standard.stringArray(forKey: DefaultsKey.windowPreviewExcludedApps) ?? []
// Persistence
UserDefaults.standard.set(sanitizedArray, forKey: DefaultsKey.windowPreviewExcludedApps)
Complex Codable Objects
For structured data like radial-menu profiles, the application encodes objects to Data using Codable helpers before storage. In Sources/Vorssaint/UI/Settings/RadialMenuSettings.swift:
// Encoding and saving
let encoded = RadialMenuSupport.encodeProfiles(profiles)
UserDefaults.standard.set(encoded, forKey: DefaultsKey.radialMenuProfiles)
// Retrieval and decoding
if let data = UserDefaults.standard.data(forKey: DefaultsKey.radialMenuProfiles) {
let profiles = RadialMenuSupport.decodeProfiles(data)
}
Key Implementation Files
Several source files demonstrate the unified persistence pattern across different subsystems:
-
Sources/Vorssaint/UI/Settings/WindowPreviewExclusionsList.swift – Manages the window-preview exclusion list UI, reading and writing bundle ID arrays via
UserDefaults.standard. -
Sources/Vorssaint/UI/Settings/ShortcutsSettings.swift – Handles shortcut-related toggles, implementing read/write cycles for keyboard preference changes.
-
Sources/Vorssaint/UI/Settings/RadialMenuSettings.swift – Persists radial-menu profiles as encoded
Dataobjects usingRadialMenuSupportencoding helpers. -
Sources/Vorssaint/Services/Update/UpdateService.swift – Stores auto-check and beta-update flags as boolean values in standard defaults.
-
Sources/Vorssaint/Services/WindowLayout/WindowLayoutSupport.swift – Reads layout spacing values and shortcut strings from persistent storage on initialization.
-
Sources/Vorssaint/Support/Uninstaller.swift – Checks a persistent flag via
UserDefaultsto determine whether to skip system sleep during cleanup operations.
Summary
- vorssaint-utils uses macOS UserDefaults as the underlying persistence mechanism for all user preferences.
- A central
DefaultsKeyenum provides type-safe string constants, preventing key-naming errors across the codebase. - The
Defaultshelper struct encapsulates sanitization logic, ensuring that raw UserDefaults data is cleaned before use. - Complex objects are persisted by encoding them to
DataviaCodableprotocols, while primitives and arrays use native UserDefaults methods. - The architecture supports both standard defaults and named suites for migration or sandboxing scenarios.
- Implementation files in
Sources/Vorssaint/UI/Settings/andSources/Vorssaint/Services/demonstrate consistent read-write-observe patterns throughout the application.
Frequently Asked Questions
Where does vorssaint-utils store user preferences?
vorssaint-utils stores all user-configurable preferences in the macOS UserDefaults system. Most settings use UserDefaults.standard, while specific components may use named suites via UserDefaults(suiteName: ...) for sandboxing or migration purposes. The OS handles persistence automatically, requiring no manual file management.
How does the app ensure type safety when accessing UserDefaults?
The application defines a central enum or struct called DefaultsKey that exports string constants for every preference. By requiring developers to use these constants instead of raw strings, the codebase prevents typos and provides compile-time checking. All access flows through these keys, ensuring consistent reads and writes across Sources/Vorssaint/UI/Settings/ and service layers.
Can vorssaint-utils settings be migrated between different UserDefaults suites?
Yes. While the majority of the codebase uses UserDefaults.standard, specific components utilize suite-based initialization with UserDefaults(suiteName: "com.apple.WindowManager") for migration or sandboxed storage scenarios. This allows the application to interact with system-level preferences or transition data between different containers when necessary.
How are complex objects like radial menu profiles persisted?
Complex objects are encoded to Data using Codable protocol implementations before storage. For example, RadialMenuSupport.encodeProfiles(profiles) converts profile objects to data that UserDefaults.standard.set(_:forKey:) can store. Retrieval reverses this process: fetching the Data value and decoding it through RadialMenuSupport.decodeProfiles(data). This pattern appears in RadialMenuSettings.swift and other settings files handling non-primitive types.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →