Persistence and the App Store

Picking between UserDefaults, files, SwiftData and the Keychain, then archiving and shipping a build for review.

Choosing where data lives

DataMechanismNotes
Preferences and flagsUserDefaultsSmall key/value; not for sensitive data or large blobs
Documents and user filesFiles in .documentDirectoryBacked up by default; visible to the Files app if you opt in
Caches and downloadsFiles in .cachesDirectorySystem may delete them under pressure; never store only here
Structured recordsSwiftData or Core DataQuery, relationships, migrations
Passwords and tokensKeychainEncrypted, survives reinstall unless you delete it explicitly
Small shared values with iCloudNSUbiquitousKeyValueStoreEventually consistent; not a database
import SwiftData
import Foundation

@Model
final class Note {
    var title: String
    var createdAt: Date

    init(title: String, createdAt: Date = .now) {
        self.title = title
        self.createdAt = createdAt
    }
}

@main
struct NotesApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
            .modelContainer(for: Note.self)
    }
}

// in a view
@Environment(\.modelContext) private var context
private func add(_ title: String) {
    context.insert(Note(title: title))
    try? context.save()
}

Reads and writes to a @Model are main-actor friendly for small apps, but heavy imports and background sync belong on a background model context. Wrap a large import in a task and save once at the end rather than after every object.

Archive, upload, review

  1. Archive the app from Xcode: choose Any iOS Device as the destination, then Product > Archive.
  2. Distribute the archive to App Store Connect. On a modern toolchain this uses a cloud-managed distribution certificate.
  3. Set the build's export compliance answer, then let it finish processing — usually minutes, occasionally hours.
  4. Attach the build to a version, fill in screenshots, description, keywords and the privacy questionnaire.
  5. Ship to TestFlight for real-device testing, then submit for review.
Bundle id      com.example.notes
Version        1.4.0        (user-visible, e.g. 1.4.0)
Build          12           (must increase for every upload)
Deployment     iOS 17.0
Devices        iPhone, iPad
  • App Review rejects apps that crash on launch, ask for data without a declared purpose, or use private APIs. The privacy nutrition label must match what the code actually collects.
  • In-app purchases, subscriptions and account deletion rules are the most common reasons a first submission is bounced.
  • Phased release lets you roll an update out to a percentage of users and pause if crash reports spike.
💡
Test on a real device on the oldest iOS version you claim to support. Simulator behaviour, especially around the camera, push notifications, Keychain sharing and performance, is not representative.

FAQ

UserDefaults or a file?
UserDefaults for a handful of small values read at launch. Move to a file or a database as soon as you are storing lists, user content, or anything you would be annoyed to lose.
Why is my build stuck on 'Processing'?
Usually a missing export compliance answer, an invalid entitlement or privacy manifest issue, or simply App Store Connect load. Check the email Apple sends to the account holder before re-uploading a new build number.

State and navigation Xcode projects and SwiftUI views

Last refreshed 2026-09-18.