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
| Data | Mechanism | Notes |
|---|---|---|
| Preferences and flags | UserDefaults | Small key/value; not for sensitive data or large blobs |
| Documents and user files | Files in .documentDirectory | Backed up by default; visible to the Files app if you opt in |
| Caches and downloads | Files in .cachesDirectory | System may delete them under pressure; never store only here |
| Structured records | SwiftData or Core Data | Query, relationships, migrations |
| Passwords and tokens | Keychain | Encrypted, survives reinstall unless you delete it explicitly |
| Small shared values with iCloud | NSUbiquitousKeyValueStore | Eventually 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
- Archive the app from Xcode: choose Any iOS Device as the destination, then Product > Archive.
- Distribute the archive to App Store Connect. On a modern toolchain this uses a cloud-managed distribution certificate.
- Set the build's export compliance answer, then let it finish processing — usually minutes, occasionally hours.
- Attach the build to a version, fill in screenshots, description, keywords and the privacy questionnaire.
- 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.
Related
State and navigation Xcode projects and SwiftUI views
Last refreshed 2026-09-18.