UserDefaults and Persistence
UserDefaults is a simple key-value store backed by a property-list file on disk, designed for small amounts of app state that need to survive relaunches — things like a user's preferred theme, whether onboarding has been completed, or the last-selected tab. It is not a database: values are loaded into memory in their entirety when the app starts, so storing large objects or collections in UserDefaults degrades performance and is explicitly discouraged by Apple. For structured or large datasets, SwiftData, Core Data, or a file on disk are the appropriate tools; UserDefaults is reserved for genuinely small, simple state.
Cricket analogy: A scorer jotting the current match's toss result and chosen ends on a small notepad is fine, but trying to log every ball of a five-day Test on that same notepad would be absurd—just as UserDefaults suits small state but not large datasets.
Reading and Writing Values
UserDefaults.standard is the shared defaults database for your app. It provides typed accessors like bool(forKey:), integer(forKey:), string(forKey:), and a generic object(forKey:), alongside set(_:forKey:) for writing. Values are stored using property-list-compatible types: String, Int, Double, Bool, Date, Data, Array, and Dictionary of those. Writes are synchronized to disk automatically by the system; you no longer need to call synchronize(), which is deprecated because the system already persists changes at appropriate times.
Cricket analogy: A scorebook has clearly labeled slots for runs (a number), wickets (a number), and the batsman's name (text), each written and read in its own typed column, mirroring UserDefaults' typed accessors like integer(forKey:) and string(forKey:).
enum SettingsKeys {
static let hasCompletedOnboarding = "hasCompletedOnboarding"
static let preferredThemeRawValue = "preferredTheme"
}
func completeOnboarding() {
UserDefaults.standard.set(true, forKey: SettingsKeys.hasCompletedOnboarding)
}
func loadOnboardingState() -> Bool {
UserDefaults.standard.bool(forKey: SettingsKeys.hasCompletedOnboarding)
}SwiftUI Integration with @AppStorage
SwiftUI provides @AppStorage, a property wrapper that reads and writes a UserDefaults value while also triggering a view update whenever that value changes — behaving like @State but backed by persistent storage. It supports the same primitive types UserDefaults supports natively, plus any RawRepresentable enum whose raw value is String or Int, which is convenient for storing enum-based settings like a theme preference directly.
Cricket analogy: A live scoreboard that auto-updates the moment the official scorer changes the run total, without anyone manually refreshing the display, mirrors @AppStorage triggering a view update the instant its backing UserDefaults value changes.
enum Theme: String {
case light, dark, system
}
struct SettingsView: View {
@AppStorage("preferredTheme") private var themeRawValue: String = Theme.system.rawValue
var body: some View {
Picker("Theme", selection: $themeRawValue) {
Text("Light").tag(Theme.light.rawValue)
Text("Dark").tag(Theme.dark.rawValue)
Text("System").tag(Theme.system.rawValue)
}
}
}Storing Codable Values
UserDefaults doesn't natively store arbitrary Codable structs, but you can bridge them by encoding to Data with JSONEncoder before writing, and decoding back with JSONDecoder after reading. This is a reasonable pattern for a small settings object, but if the data grows into a real collection — a list of downloaded items, a user's saved articles — that's a signal to move to SwiftData or a dedicated file rather than continuing to stretch UserDefaults.
Cricket analogy: Jotting a fielding-position preference as a coded note works for one small setting, but once you're tracking an entire growing list of a bowler's dismissals across a season, it's time to move from sticky notes to a proper scorebook database, mirroring the shift from UserDefaults to SwiftData.
UserDefaults is not encrypted and should never be used to store sensitive data like passwords or auth tokens — the Keychain is the correct, encrypted storage location for secrets on Apple platforms.
Because UserDefaults loads its entire contents into memory at process launch, storing large arrays, images, or big Codable blobs in it can noticeably slow app launch. Treat it strictly as a store for small, simple preferences.
- UserDefaults is a lightweight, property-list-backed key-value store intended for small settings, not large datasets.
UserDefaults.standardoffers typed accessors (bool, integer, string, object) for reading and writing.@AppStoragebinds a UserDefaults key directly into SwiftUI state, triggering view updates on change.- Codable structs can be bridged into UserDefaults by encoding to Data, but this doesn't scale to large collections.
- Sensitive data (passwords, tokens) belongs in the Keychain, never in UserDefaults, since it is unencrypted.
synchronize()is deprecated; the system persists UserDefaults writes automatically.
Practice what you learned
1. What underlying format backs UserDefaults storage?
2. What does the @AppStorage property wrapper provide beyond plain UserDefaults access?
3. Where should sensitive data like an authentication token be stored instead of UserDefaults?
4. How can you store a Codable struct in UserDefaults?
5. Why is calling UserDefaults.standard.synchronize() unnecessary in modern code?
Was this page helpful?
You May Also Like
SwiftData Basics
Learn how SwiftData lets you model, persist, and query app data using plain Swift types annotated with macros, replacing much of Core Data's boilerplate.
Codable and JSON Parsing
Learn how Swift's Codable protocol pair enables automatic, type-safe encoding and decoding between Swift types and formats like JSON.
@State and @Binding
Understand how @State owns local view state and how @Binding lets child views read and write that state without owning it themselves.
App Lifecycle in SwiftUI
How SwiftUI apps start, move through foreground/background states, and respond to system events using the App protocol, Scene, and scenePhase.