What belongs in UserDefaults, and how do you design and test around it?
Predict first
What does this print?
let d = UserDefaults(suiteName: "demo4")!
d.register(defaults: ["a": 1])
d.set(2, forKey: "a")
d.removeObject(forKey: "a")
print(d.integer(forKey: "a"))
- 0
- 1
- 2
Mental model. UserDefaults is a coat pocket: good for keys and a train ticket, wrong for the whole wardrobe or your passport.
Keep small, non-sensitive preferences and flags in UserDefaults. Secrets and tokens go in the Keychain; big or queryable data goes in files, Core Data or SwiftData. All defaults are loaded into memory with the app, so large blobs there slow things down and have no place to be queried.
Design it like any API: wrap it in a typed Settings type (or @AppStorage in SwiftUI) so key strings live in one place; version your keys and migrate at launch; observe changes through UserDefaults.didChangeNotification or KVO. In tests, inject a throwaway suite (UserDefaults(suiteName: UUID().uuidString)) and clear it with removePersistentDomain. Since 2024 Apple treats UserDefaults as a required-reason API: your privacy manifest must declare why the app uses it.
Registered defaults are the fallback beneath your saved values. Removing the saved value reveals the default again.
| UserDefaults | Keychain | File | SwiftData | |
|---|---|---|---|---|
| Best for | small settings | secrets | documents, blobs | queryable models |
| Encrypted | no | yes | optional | optional |
| Removed with the app | yes | often kept | yes | yes |
| Size | small | small | large | large |
Common traps
- Scattering string keys through the code. A typo creates a silent new setting.
- Using it for tokens. It is plain on disk and included in backups. Use the Keychain.
They might follow up with
- Does it survive deleting the app? No. UserDefaults goes with the app; Keychain items can remain.
- How do you rename a key without losing data? Read the old key once, write the new one, remove the old.
Related questions
- 2.5.7 What is UserDefaults, what can it store, and why do its typed getters mislead?
← What is UserDefaults, what can it store, and why do its typed getters…