iOS Storage Guide
Purpose: Navigation hub for ALL storage decisions β database vs files, local vs cloud, specific locations
iOS Version: iOS 17+ (iOS 26+ for latest features)
Context: Complete storage decision framework integrating SwiftData (WWDC 2023), CKSyncEngine (WWDC 2023), and file management best practices
When to Use This Skill
β
Use this skill when:
- Starting a new project and choosing storage approach
- Asking "where should I store this data?"
- Deciding between SwiftData, Core Data, SQLite, or files
- Choosing between CloudKit and iCloud Drive for sync
- Determining Documents vs Caches vs Application Support
- Planning data architecture for offline/online scenarios
- Migrating from one storage solution to another
- Debugging "files disappeared" or "data not syncing"
β Do NOT use this skill for:
- SwiftData implementation details (use
axiom-swiftdata skill)
- SQLite/GRDB specifics (use
axiom-sqlitedata or axiom-grdb skills)
- CloudKit sync implementation (use
axiom-cloudkit-ref skill)
- File protection APIs (use
axiom-file-protection-ref skill)
Related Skills:
- Existing database skills:
axiom-swiftdata, axiom-sqlitedata, axiom-grdb
- New file skills:
axiom-file-protection-ref, axiom-storage-management-ref, axiom-storage-diag
- New cloud skills:
axiom-cloudkit-ref, axiom-icloud-drive-ref, axiom-cloud-sync-diag
Core Philosophy
"Choose the right tool for your data shape. Then choose the right location."
Storage decisions have two dimensions:
- Format: How is data structured? (Queryable records vs files)
- Location: Where is it stored? (Local vs cloud, which directory)
Getting the format wrong forces workarounds. Getting the location wrong causes data loss or backup bloat.
The Complete Decision Tree
Level 1: Format β What Are You Storing?
What is the shape of your data?
ββ STRUCTURED DATA (queryable records, relationships, search)
β Examples: User profiles, task lists, notes, contacts, transactions
β β Continue to "Structured Data Path" below
β
ββ FILES (documents, images, videos, downloads, caches)
Examples: Photos, PDFs, downloaded content, thumbnails, temp files
β Continue to "File Storage Path" below
Structured Data Path
Modern Apps (iOS 17+)
import SwiftData
@Model
class Task {
var title: String
var isCompleted: Bool
var dueDate: Date
init(title: String, isCompleted: Bool = false, dueDate: Date) {
self.title = title
self.isCompleted = isCompleted
self.dueDate = dueDate
}
}
@Query(sort: \Task.dueDate) var tasks: [Task]
Why SwiftData:
- Modern Swift-native API (no Objective-C)
- Type-safe queries
- Built-in CloudKit sync support
- Observable models integrate with SwiftUI
- Use skill:
axiom-swiftdata for implementation details
When NOT to use SwiftData:
- Need advanced SQLite features (FTS5, complex joins)
- Existing Core Data app (migration overhead)
- Ultra-performance-critical (direct SQLite is faster)
Advanced Control Needed
import SQLiteData
let results = try db.prepare("SELECT * FROM users WHERE name MATCH ?", "John")
Use SQLiteData when:
- Need full-text search (FTS5)
- Custom SQL queries and indices
- Maximum performance (direct SQLite)
- Migration from existing SQLite database
- Use skill:
axiom-sqlitedata for modern SQLite patterns
Use GRDB when:
- Need reactive queries (ValueObservation)
- Complex database operations
- Type-safe query builders
- Use skill:
axiom-grdb for advanced patterns
Legacy Apps (iOS 16 and earlier)
import CoreData
Only use Core Data if:
- Maintaining existing Core Data app
- Can't upgrade to iOS 17 minimum deployment
File Storage Path
Decision Tree for Files
What kind of file is it?
ββ USER-CREATED CONTENT (documents, photos created by user)
β Where: Documents/ directory
β Backed up: β
Yes (iCloud/iTunes)
β Purged: β Never
β Visible in Files app: β
Yes
β Example: User's edited photos, documents, exported data
β β See "Documents Directory" section below
β
ββ APP-GENERATED DATA (not user-visible, must persist)
β Where: Library/Application Support/
β Backed up: β
Yes
β Purged: β Never
β Visible in Files app: β No
β Example: Database files, user settings, downloaded assets
β β See "Application Support Directory" section below
β
ββ RE-DOWNLOADABLE / REGENERABLE CONTENT
β Where: Library/Caches/
β Backed up: β No (set isExcludedFromBackup)
β Purged: β
Yes (under storage pressure)
β Example: Thumbnails, API responses, downloaded images
β β See "Caches Directory" section below
β
ββ TEMPORARY FILES (can be deleted anytime)
Where: tmp/
Backed up: β No
Purged: β
Yes (aggressive, even while app running)
Example: Image processing intermediates, export staging
β See "Temporary Directory" section below
Documents Directory
func saveUserDocument(_ data: Data, filename: String) throws {
let documentsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let fileURL = documentsURL.appendingPathComponent(filename)
try data.write(to: fileURL, options: .completeFileProtection)
}
Key rules:
- β
DO store: User-created documents, exported files, user-visible content
- β DON'T store: Downloaded data that can be re-fetched, caches, temp files
- β οΈ WARNING: Everything here is backed up to iCloud. Large re-downloadable files will bloat backups and may get your app rejected.
Use skill: axiom-file-protection-ref for encryption options
Application Support Directory
func getAppDataURL() -> URL {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let appDataURL = appSupportURL.appendingPathComponent(
Bundle.main.bundleIdentifier ?? "AppData"
)
try? FileManager.default.createDirectory(
at: appDataURL,
withIntermediateDirectories: true
)
return appDataURL
}
Use for:
- SwiftData/SQLite database files
- User preferences
- Downloaded assets that must persist
- Configuration files
Caches Directory
func cacheDownloadedImage(data: Data, for url: URL) throws {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let filename = url.lastPathComponent
let fileURL = cacheURL.appendingPathComponent(filename)
try data.write(to: fileURL)
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try fileURL.setResourceValues(resourceValues)
}
Key rules:
- β
The system CAN and WILL delete files here under storage pressure
- β
Always have a way to re-download or regenerate
- β Don't store anything that can't be recreated
Use skill: axiom-storage-management-ref for purge policies and disk space management
Temporary Directory
func processImageWithTempFile(image: UIImage) throws {
let tmpURL = FileManager.default.temporaryDirectory
let tempFileURL = tmpURL.appendingPathComponent(UUID().uuidString + ".jpg")
try image.jpegData(compressionQuality: 0.8)?