Skillshub axiom-storage
Use when asking 'where should I store this data', 'should I use SwiftData or files', 'CloudKit vs iCloud Drive', 'Documents vs Caches', 'local or cloud storage', 'how do I sync data', 'where do app files go' - comprehensive decision framework for all iOS storage options
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/CharlesWiltgen/Axiom/axiom-storage" ~/.claude/skills/comeonoliver-skillshub-axiom-storage && rm -rf "$T"
skills/CharlesWiltgen/Axiom/axiom-storage/SKILL.mdiOS 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
skill)axiom-swiftdata - SQLite/GRDB specifics (use
oraxiom-sqlitedata
skills)axiom-grdb - CloudKit sync implementation (use
skill)axiom-cloudkit-ref - File protection APIs (use
skill)axiom-file-protection-ref
Related Skills:
- Existing database skills:
,axiom-swiftdata
,axiom-sqlitedataaxiom-grdb - New file skills:
,axiom-file-protection-ref
,axiom-storage-management-refaxiom-storage-diag - New cloud skills:
,axiom-cloudkit-ref
,axiom-icloud-drive-refaxiom-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+)
// ✅ CORRECT: SwiftData for modern structured persistence 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 with type safety @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:
for implementation detailsaxiom-swiftdata
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
// ✅ CORRECT: SQLiteData or GRDB for advanced features import SQLiteData // Full-text search, custom indices, raw SQL when needed 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:
for modern SQLite patternsaxiom-sqlitedata
Use GRDB when:
- Need reactive queries (ValueObservation)
- Complex database operations
- Type-safe query builders
- Use skill:
for advanced patternsaxiom-grdb
Legacy Apps (iOS 16 and earlier)
// ❌ LEGACY: Core Data (avoid for new projects) import CoreData // NSManagedObject, NSFetchRequest, NSPredicate...
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
// ✅ CORRECT: User-created content in Documents func saveUserDocument(_ data: Data, filename: String) throws { let documentsURL = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask )[0] let fileURL = documentsURL.appendingPathComponent(filename) // Enable file protection 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
// ✅ CORRECT: App data in Application Support func getAppDataURL() -> URL { let appSupportURL = FileManager.default.urls( for: .applicationSupportDirectory, in: .userDomainMask )[0] // Create app-specific subdirectory 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
// ✅ CORRECT: Re-downloadable content in Caches 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) // Mark as excluded from backup (explicit, though Caches is auto-excluded) 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
// ✅ CORRECT: Truly temporary files in tmp func processImageWithTempFile(image: UIImage) throws { let tmpURL = FileManager.default.temporaryDirectory let tempFileURL = tmpURL.appendingPathComponent(UUID().uuidString + ".jpg") // Write temp file try image.jpegData(compressionQuality: 0.8)?.write(to: tempFileURL) // Process... processImage(at: tempFileURL) // Clean up (though system will auto-clean eventually) try? FileManager.default.removeItem(at: tempFileURL) }
Key rules:
- System can delete files here AT ANY TIME (even while app is running)
- Always clean up after yourself
- Don't rely on files persisting between app launches
Cloud Storage Decisions
Should Data Sync to Cloud?
Does this data need to sync across user's devices? ├─ NO → Use local storage (paths above) │ └─ YES → What kind of data? │ ├─ STRUCTURED DATA (queryable, relationships) │ → Use CloudKit │ → See "CloudKit Path" below │ ├─ FILES (documents, images) │ → Use iCloud Drive (ubiquitous containers) │ → See "iCloud Drive Path" below │ └─ SMALL PREFERENCES (<1 MB, key-value pairs) → Use NSUbiquitousKeyValueStore → See "Key-Value Store" below
CloudKit Path (Structured Data Sync)
// ✅ CORRECT: SwiftData with CloudKit sync (iOS 17+) import SwiftData let container = try ModelContainer( for: Task.self, configurations: ModelConfiguration( cloudKitDatabase: .private("iCloud.com.example.app") ) )
Three approaches to CloudKit:
-
SwiftData + CloudKit (Recommended, iOS 17+):
- Automatic sync for SwiftData models
- Private database only
- Easiest approach
- Use skill:
for detailsaxiom-swiftdata
-
CKSyncEngine (Custom persistence, iOS 17+):
- For SQLite, GRDB, or custom stores
- Manages sync automatically
- Modern replacement for manual CloudKit
- Use skill:
for CKSyncEngine patternsaxiom-cloudkit-ref
-
Raw CloudKit APIs (Legacy):
- CKContainer, CKDatabase, CKRecord
- Manual sync management
- Only if CKSyncEngine doesn't fit
- Use skill:
for raw API referenceaxiom-cloudkit-ref
iCloud Drive Path (File Sync)
// ✅ CORRECT: iCloud Drive for file-based sync func saveToICloud(_ data: Data, filename: String) throws { // Get ubiquitous container guard let iCloudURL = FileManager.default.url( forUbiquityContainerIdentifier: nil ) else { throw StorageError.iCloudUnavailable } let documentsURL = iCloudURL.appendingPathComponent("Documents") try FileManager.default.createDirectory( at: documentsURL, withIntermediateDirectories: true ) let fileURL = documentsURL.appendingPathComponent(filename) try data.write(to: fileURL) }
When to use iCloud Drive:
- User-created documents that sync
- File-based collaboration
- Simple file sync (like Dropbox)
Use skill:
axiom-icloud-drive-ref for implementation details
Key-Value Store (Small Preferences)
// ✅ CORRECT: Small synced preferences let store = NSUbiquitousKeyValueStore.default store.set(true, forKey: "darkModeEnabled") store.set(2.0, forKey: "textSize") store.synchronize()
Limitations:
- Max 1 MB total storage
- Max 1024 keys
- Max 1 MB per value
- For preferences ONLY, not data storage
Common Patterns and Anti-Patterns
✅ DO: Choose Based on Data Shape
// ✅ CORRECT: Structured data → SwiftData @Model class Note { var title: String var content: String var tags: [Tag] // Relationships } // ✅ CORRECT: Files → FileManager + proper directory let imageData = capturedPhoto.jpegData(compressionQuality: 0.9) try imageData?.write(to: documentsURL.appendingPathComponent("photo.jpg"))
❌ DON'T: Use Files for Structured Data
// ❌ WRONG: Storing queryable data as JSON files let tasks = [Task(...), Task(...), Task(...)] let jsonData = try JSONEncoder().encode(tasks) try jsonData.write(to: appSupportURL.appendingPathComponent("tasks.json")) // Why it's wrong: // - Can't query individual tasks // - Can't filter or sort efficiently // - No relationships // - Entire file loaded into memory // - Concurrent access issues // ✅ CORRECT: Use SwiftData instead @Model class Task { ... }
❌ DON'T: Store Re-downloadable Content in Documents
// ❌ WRONG: Downloaded images in Documents (bloats backup!) func downloadProfileImage(url: URL) throws { let data = try Data(contentsOf: url) let documentsURL = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask )[0] try data.write(to: documentsURL.appendingPathComponent("profile.jpg")) } // ✅ CORRECT: Use Caches instead func downloadProfileImage(url: URL) throws { let data = try Data(contentsOf: url) let cacheURL = FileManager.default.urls( for: .cachesDirectory, in: .userDomainMask )[0] let fileURL = cacheURL.appendingPathComponent("profile.jpg") try data.write(to: fileURL) // Mark excluded from backup var resourceValues = URLResourceValues() resourceValues.isExcludedFromBackup = true try fileURL.setResourceValues(resourceValues) }
❌ DON'T: Use CloudKit for Simple File Sync
// ❌ WRONG: Storing files as CKAssets with manual sync let asset = CKAsset(fileURL: documentURL) let record = CKRecord(recordType: "Document") record["file"] = asset // ... manual upload, conflict handling, etc. // ✅ CORRECT: Use iCloud Drive for files // Files automatically sync via ubiquitous container try data.write(to: iCloudDocumentsURL.appendingPathComponent("doc.pdf"))
Quick Reference Table
| Data Type | Format | Local Location | Cloud Sync | Use Skill |
|---|---|---|---|---|
| User tasks, notes | Structured | Application Support | SwiftData + CloudKit | → |
| User photos (created) | File | Documents | iCloud Drive | → |
| Downloaded images | File | Caches | None (re-download) | |
| Thumbnails | File | Caches | None (regenerate) | |
| Database file | File | Application Support | CKSyncEngine (if custom) | → |
| Temp processing | File | tmp | None | N/A |
| User settings | Key-Value | UserDefaults | NSUbiquitousKeyValueStore | N/A |
tvOS Storage
tvOS has no persistent local storage. This catches every iOS developer.
| Directory | tvOS Behavior |
|---|---|
| Documents | Does not exist |
| Application Support | System can delete when app is not running |
| Caches | System deletes at any time |
| tmp | System deletes at any time |
| UserDefaults | 500 KB limit (vs ~4 MB on iOS) |
Every local file can vanish between app launches. Your tvOS app must survive starting from zero.
Recommended: Use iCloud (CloudKit, NSUbiquitousKeyValueStore, or iCloud Drive) as primary storage. Treat local files as cache only. See
axiom-tvos for full tvOS storage patterns.
Debugging: Data Missing or Not Syncing?
Files disappeared:
- Check if stored in Caches or tmp (system purged them)
- Check file protection level (may be inaccessible when locked)
- Use skill:
axiom-storage-diag
Backup too large:
- Check if re-downloadable content is in Documents (should be in Caches)
- Check if
is set on large filesisExcludedFromBackup - Use skill:
axiom-storage-management-ref
Data not syncing:
- CloudKit: Check CKSyncEngine status, account availability
- Use skill:
axiom-cloud-sync-diag
- Use skill:
- iCloud Drive: Check ubiquitous container entitlements, file coordinator
- Use skill:
,axiom-icloud-drive-refaxiom-cloud-sync-diag
- Use skill:
Migration Checklist
When changing storage approach:
Database to Database (e.g., Core Data → SwiftData):
- Create SwiftData models matching Core Data entities
- Write migration code to copy data
- Test with production-size datasets
- Keep old database for rollback
Files to Database:
- Identify all JSON/plist files storing structured data
- Create SwiftData models
- Write one-time migration on first launch
- Verify all data migrated, then delete old files
Local to Cloud:
- Ensure proper entitlements (CloudKit/iCloud)
- Handle initial upload carefully (bandwidth)
- Test conflict resolution
- Provide user control (opt-in)
Last Updated: 2025-12-12 Skill Type: Discipline Related WWDC Sessions:
- WWDC 2023-10187: Meet SwiftData
- WWDC 2023-10188: Sync to iCloud with CKSyncEngine
- WWDC 2024-10137: What's new in SwiftData