iOS path · Doing the work · Topic 7 of 11

Persistence: defaults, files, SwiftData

Not started

An iOS app can only write inside its own container, but within it you have several stores, each with different rules about size, security, backup and lifetime. Choosing the right one is mostly about the data: is it a setting, a secret, a file, or a set of related records you need to query?

Why it matters: Put secrets in UserDefaults or big files in the wrong folder and you get leaks, bloated backups, or data the system deletes when space runs low.

  1. 1UserDefaults for settings

    Small, non-sensitive preferences; stored unencrypted in a property list.

  2. 2Keychain for secrets

    Tokens and passwords go in the Keychain, which is encrypted and survives reinstall.

  3. 3Folders mean something

    Documents and Application Support are backed up; Caches and tmp may be purged.

  4. 4SwiftData for object graphs

    Models you query, relate and change belong in SwiftData (iOS 17+) or Core Data.

1

How it works

The explanation, with examples you can run.

Settings: UserDefaults

UserDefaults.standard.set(true, forKey: "hasSeenOnboarding")let seen = UserDefaults.standard.bool(forKey: "hasSeenOnboarding") // In SwiftUI, @AppStorage reads and writes the same store and refreshes the view.@AppStorage("sortOrder") private var sortOrder = "date"

UserDefaults is a key-value store of property-list types (strings, numbers, dates, data, arrays and dictionaries of them). Writes update memory at once and reach disk asynchronously. It is loaded as a whole, so keep it small, and it is not encrypted: Apple's own documentation says to keep personal or sensitive data out of it. Using it also means declaring a reason in your privacy manifest (see the privacy topic).

Secrets: the Keychain

Passwords, tokens and keys belong in the Keychain (Security framework, SecItemAdd and friends). Items are encrypted, can require the device to be unlocked or biometrics, and can be shared between your apps through an access group. Unlike your container, Keychain items can survive deleting and reinstalling the app, so don't rely on reinstall to clear them.

Files: pick the folder on purpose

FolderBacked upPurged by systemUse for
DocumentsYesNoFiles the user creates; can be shown in the Files app
Library/Application SupportYesNoYour app's own data files and databases
Library/CachesNoYes, when space is lowAnything you can download or rebuild
tmpNoYesScratch files you delete when done
let support = try FileManager.default.url(for: .applicationSupportDirectory,                                          in: .userDomainMask, appropriateFor: nil, create: true)try data.write(to: support.appending(path: "drafts.json"), options: [.atomic, .completeFileProtection])

Write atomically so a crash or kill mid-write can't leave half a file. Files get data protection from the passcode: by default a file is unreadable until the first unlock after a reboot, and .completeFileProtection locks it whenever the device is locked (which also means you can't read it from a background task while locked). Large re-downloadable files outside Caches can be marked with isExcludedFromBackup.

Records: SwiftData or Core Data

For many related objects that you filter, sort and change, use a database. SwiftData (iOS 17) describes the schema in Swift and is built on Core Data's storage:

@Model final class Trip {    var name: String    var start: Date    @Relationship(deleteRule: .cascade) var stops: [Stop] = []    init(name: String, start: Date) { self.name = name; self.start = start }} struct TripsView: View {    @Query(sort: \Trip.start) private var trips: [Trip]   // refreshes the view on change    @Environment(\.modelContext) private var context    var body: some View { List(trips) { Text($0.name) } }}

@Model classes are observable, so SwiftUI updates as they change. The container's mainContext is bound to the main actor and autosaves; heavy imports belong in a ModelActor with its own context, passing PersistentIdentifiers between actors rather than model objects. Core Data remains the choice when you need to support iOS 16 and earlier, or features SwiftData doesn't have yet. Neither replaces your server: treat a local database as a cache plus offline edits, and plan how changes sync.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Where should your app keep the user's OAuth refresh token?
  2. 2.Your app generates thumbnails it can rebuild from the originals. Where should they go?
  3. 3.When does a SwiftData model context save changes for you?

0 of 3 right so far. Get all three to finish this topic.