axiom-storage
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiOS Storage Guide
iOS 存储指南
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
用途:所有存储决策的导航中心——数据库 vs 文件、本地 vs 云端、具体存储位置的选择
iOS版本要求:iOS 17+(最新功能需要iOS 26+)
背景说明:整合了SwiftData(WWDC 2023)、CKSyncEngine(WWDC 2023)和文件管理最佳实践的完整存储决策框架
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 or
axiom-sqlitedataskills)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
✅ 在以下场景使用本技能:
- 启动新项目并选择存储方案时
- 询问“我应该把数据存在哪里?”时
- 需要在SwiftData、Core Data、SQLite或文件存储之间做选择时
- 为同步功能选择CloudKit还是iCloud Drive时
- 确定使用Documents、Caches还是Application Support目录时
- 规划离线/在线场景下的数据架构时
- 从一种存储方案迁移到另一种时
- 调试“文件消失”或“数据不同步”问题时
❌ 请勿在以下场景使用本技能:
- SwiftData的实现细节(使用技能)
axiom-swiftdata - SQLite/GRDB的具体用法(使用或
axiom-sqlitedata技能)axiom-grdb - CloudKit同步的实现细节(使用技能)
axiom-cloudkit-ref - 文件保护API相关内容(使用技能)
axiom-file-protection-ref
相关技能:
- 现有数据库技能:、
axiom-swiftdata、axiom-sqlitedataaxiom-grdb - 新文件管理技能:、
axiom-file-protection-ref、axiom-storage-management-refaxiom-storage-diag - 新云服务技能:、
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.
“根据数据形态选择合适的工具,再选择合适的存储位置。”
存储决策包含两个维度:
- 格式:数据的结构是什么?(可查询记录 vs 文件)
- 位置:数据存在哪里?(本地 vs 云端,具体哪个目录)
格式选择错误会导致后续需要大量变通方案,位置选择错误会造成数据丢失或备份臃肿。
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你的数据形态是什么?
├─ 结构化数据(可查询记录、关联关系、可搜索)
│ 示例:用户档案、任务列表、笔记、联系人、交易记录
│ → 继续查看下方“结构化数据存储路径”
│
└─ 文件(文档、图片、视频、下载内容、缓存文件)
示例:照片、PDF、下载内容、缩略图、临时文件
→ 继续查看下方“文件存储路径”Structured Data Path
结构化数据存储路径
Modern Apps (iOS 17+)
现代应用(iOS 17+)
swift
// ✅ 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 details
axiom-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)
swift
// ✅ 正确做法:使用SwiftData实现现代结构化持久化
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]为什么选择SwiftData:
- 现代化的Swift原生API(无需Objective-C)
- 类型安全的查询
- 内置CloudKit同步支持
- 可观察模型与SwiftUI无缝集成
- 技能参考:实现细节请使用技能
axiom-swiftdata
何时不使用SwiftData:
- 需要高级SQLite功能(FTS5、复杂关联查询)
- 现有项目基于Core Data(迁移成本高)
- 对性能要求极高(直接使用SQLite速度更快)
Advanced Control Needed
需要高级控制场景
swift
// ✅ 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 patterns
axiom-sqlitedata
Use GRDB when:
- Need reactive queries (ValueObservation)
- Complex database operations
- Type-safe query builders
- Use skill: for advanced patterns
axiom-grdb
swift
// ✅ 正确做法:使用SQLiteData或GRDB实现高级功能
import SQLiteData
// 支持全文搜索、自定义索引、必要时使用原生SQL
let results = try db.prepare("SELECT * FROM users WHERE name MATCH ?", "John")使用SQLiteData的场景:
- 需要全文搜索(FTS5)
- 自定义SQL查询和索引
- 追求极致性能(直接操作SQLite)
- 从现有SQLite数据库迁移
- 技能参考:现代SQLite使用模式请参考技能
axiom-sqlitedata
使用GRDB的场景:
- 需要响应式查询(ValueObservation)
- 复杂数据库操作
- 类型安全的查询构建器
- 技能参考:高级使用模式请参考技能
axiom-grdb
Legacy Apps (iOS 16 and earlier)
遗留应用(iOS 16及更早版本)
swift
// ❌ 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
swift
// ❌ 遗留方案:Core Data(新项目避免使用)
import CoreData
// NSManagedObject、NSFetchRequest、NSPredicate...仅在以下场景使用Core Data:
- 维护现有基于Core Data的应用
- 无法将最低部署版本升级到iOS 17
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/目录
│ 是否备份:✅ 是(iCloud/iTunes)
│ 是否会被清理:❌ 永不
│ 是否在“文件”应用中可见:✅ 是
│ 示例:用户编辑的照片、文档、导出的数据
│ → 查看下方“Documents目录”章节
│
├─ 应用生成的数据(对用户不可见,但需要持久化)
│ 存储位置:Library/Application Support/
│ 是否备份:✅ 是
│ 是否会被清理:❌ 永不
│ 是否在“文件”应用中可见:❌ 否
│ 示例:数据库文件、用户设置、下载的资源
│ → 查看下方“Application Support目录”章节
│
├─ 可重新下载/可重新生成的内容
│ 存储位置:Library/Caches/
│ 是否备份:❌ 否(需设置isExcludedFromBackup)
│ 是否会被清理:✅ 是(存储空间不足时)
│ 示例:缩略图、API响应结果、下载的图片
│ → 查看下方“Caches目录”章节
│
└─ 临时文件(可随时删除)
存储位置:tmp/
是否备份:❌ 否
是否会被清理:✅ 是(系统会主动清理,甚至应用运行时也会)
示例:图片处理中间文件、导出临时文件
→ 查看下方“临时目录”章节Documents Directory
Documents目录
swift
// ✅ 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: for encryption options
axiom-file-protection-refswift
// ✅ 正确做法:用户创建的内容存储在Documents目录
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)
}核心规则:
- ✅ 建议存储:用户创建的文档、导出文件、对用户可见的内容
- ❌ 禁止存储:可重新获取的下载数据、缓存文件、临时文件
- ⚠️ 注意:此目录下的所有内容都会备份到iCloud,大型可重新下载文件会导致备份臃肿,甚至可能导致应用被拒
技能参考:加密选项请参考技能
axiom-file-protection-refApplication Support Directory
Application Support目录
swift
// ✅ 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
swift
// ✅ 正确做法:应用数据存储在Application Support目录
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
}适用场景:
- SwiftData/SQLite数据库文件
- 用户偏好设置
- 需要持久化的下载资源
- 配置文件
Caches Directory
Caches目录
swift
// ✅ 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: for purge policies and disk space management
axiom-storage-management-refswift
// ✅ 正确做法:可重新下载的内容存储在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)
// 标记为排除备份(虽然Caches目录默认已排除,但显式设置更稳妥)
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try fileURL.setResourceValues(resourceValues)
}核心规则:
- ✅ 系统会在存储空间不足时删除此目录下的文件
- ✅ 始终要有重新下载或重新生成的方案
- ❌ 不要存储任何无法重新创建的内容
技能参考:清理策略和磁盘空间管理请参考技能
axiom-storage-management-refTemporary Directory
临时目录
swift
// ✅ 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
swift
// ✅ 正确做法:真正的临时文件存储在tmp目录
func processImageWithTempFile(image: UIImage) throws {
let tmpURL = FileManager.default.temporaryDirectory
let tempFileURL = tmpURL.appendingPathComponent(UUID().uuidString + ".jpg")
// 写入临时文件
try image.jpegData(compressionQuality: 0.8)?.write(to: tempFileURL)
// 处理图片...
processImage(at: tempFileURL)
// 清理临时文件(虽然系统最终会自动清理,但主动清理更佳)
try? FileManager.default.removeItem(at: tempFileURL)
}核心规则:
- 系统可以随时删除此目录下的文件(甚至应用运行时)
- 始终要主动清理临时文件
- 不要依赖临时文件在应用重启后依然存在
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
│ → 查看下方“CloudKit路径”
│
├─ 文件(文档、图片)
│ → 使用iCloud Drive(通用容器)
│ → 查看下方“iCloud Drive路径”
│
└─ 小型偏好设置(<1 MB,键值对)
→ 使用NSUbiquitousKeyValueStore
→ 查看下方“键值存储”CloudKit Path (Structured Data Sync)
CloudKit路径(结构化数据同步)
swift
// ✅ 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 details
axiom-swiftdata
-
CKSyncEngine (Custom persistence, iOS 17+):
- For SQLite, GRDB, or custom stores
- Manages sync automatically
- Modern replacement for manual CloudKit
- Use skill: for CKSyncEngine patterns
axiom-cloudkit-ref
-
Raw CloudKit APIs (Legacy):
- CKContainer, CKDatabase, CKRecord
- Manual sync management
- Only if CKSyncEngine doesn't fit
- Use skill: for raw API reference
axiom-cloudkit-ref
swift
// ✅ 正确做法:SwiftData结合CloudKit同步(iOS 17+)
import SwiftData
let container = try ModelContainer(
for: Task.self,
configurations: ModelConfiguration(
cloudKitDatabase: .private("iCloud.com.example.app")
)
)CloudKit的三种使用方式:
-
SwiftData + CloudKit(推荐,iOS 17+):
- SwiftData模型自动同步
- 仅支持私有数据库
- 实现最简单
- 技能参考:细节请参考技能
axiom-swiftdata
-
CKSyncEngine(自定义持久化,iOS 17+):
- 适用于SQLite、GRDB或自定义存储方案
- 自动管理同步流程
- 替代手动CloudKit集成的现代化方案
- 技能参考:CKSyncEngine使用模式请参考技能
axiom-cloudkit-ref
-
原生CloudKit API(遗留方案):
- CKContainer、CKDatabase、CKRecord
- 手动管理同步流程
- 仅在CKSyncEngine无法满足需求时使用
- 技能参考:原生API参考请使用技能
axiom-cloudkit-ref
iCloud Drive Path (File Sync)
iCloud Drive路径(文件同步)
swift
// ✅ 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: for implementation details
axiom-icloud-drive-refswift
// ✅ 正确做法:使用iCloud Drive实现基于文件的同步
func saveToICloud(_ data: Data, filename: String) throws {
// 获取通用容器
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)
}使用iCloud Drive的场景:
- 需要同步的用户创建文档
- 基于文件的协作
- 简单的文件同步(类似Dropbox)
技能参考:实现细节请参考技能
axiom-icloud-drive-refKey-Value Store (Small Preferences)
键值存储(小型偏好设置)
swift
// ✅ 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
swift
// ✅ 正确做法:小型同步偏好设置使用此方案
let store = NSUbiquitousKeyValueStore.default
store.set(true, forKey: "darkModeEnabled")
store.set(2.0, forKey: "textSize")
store.synchronize()限制:
- 总存储容量最大1 MB
- 最多支持1024个键
- 单个值最大1 MB
- 仅适用于偏好设置,不适合数据存储
Common Patterns and Anti-Patterns
常见模式与反模式
✅ DO: Choose Based on Data Shape
✅ 正确做法:根据数据形态选择方案
swift
// ✅ 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"))swift
// ✅ 正确做法:结构化数据 → 使用SwiftData
@Model
class Note {
var title: String
var content: String
var tags: [Tag] // 关联关系
}
// ✅ 正确做法:文件 → 使用FileManager + 合适的目录
let imageData = capturedPhoto.jpegData(compressionQuality: 0.9)
try imageData?.write(to: documentsURL.appendingPathComponent("photo.jpg"))❌ DON'T: Use Files for Structured Data
❌ 错误做法:用文件存储结构化数据
swift
// ❌ 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 { ... }swift
// ❌ 错误:将可查询数据存储为JSON文件
let tasks = [Task(...), Task(...), Task(...)]
let jsonData = try JSONEncoder().encode(tasks)
try jsonData.write(to: appSupportURL.appendingPathComponent("tasks.json"))
// 错误原因:
// - 无法查询单个任务
// - 无法高效过滤或排序
// - 不支持关联关系
// - 整个文件需要加载到内存
// - 并发访问存在问题
// ✅ 正确做法:改用SwiftData
@Model class Task { ... }❌ DON'T: Store Re-downloadable Content in Documents
❌ 错误做法:将可重新下载的内容存在Documents目录
swift
// ❌ 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)
}swift
// ❌ 错误:下载的图片存在Documents目录(会导致备份臃肿!)
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"))
}
// ✅ 正确做法:改用Caches目录
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)
// 标记为排除备份
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try fileURL.setResourceValues(resourceValues)
}❌ DON'T: Use CloudKit for Simple File Sync
❌ 错误做法:用CloudKit实现简单文件同步
swift
// ❌ 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"))swift
// ❌ 错误:将文件作为CKAssets存储并手动同步
let asset = CKAsset(fileURL: documentURL)
let record = CKRecord(recordType: "Document")
record["file"] = asset
// ... 手动上传、冲突处理等
// ✅ 正确做法:使用iCloud Drive存储文件
// 文件会通过通用容器自动同步
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 |
| 数据类型 | 格式 | 本地存储位置 | 云端同步方案 | 参考技能 |
|---|---|---|---|---|
| 用户任务、笔记 | 结构化 | Application Support | SwiftData + CloudKit | |
| 用户拍摄的照片 | 文件 | Documents | iCloud Drive | |
| 下载的图片 | 文件 | Caches | 无(重新下载) | |
| 缩略图 | 文件 | Caches | 无(重新生成) | |
| 数据库文件 | 文件 | Application Support | CKSyncEngine(自定义存储时) | |
| 临时处理文件 | 文件 | tmp | 无 | N/A |
| 用户设置 | 键值对 | UserDefaults | NSUbiquitousKeyValueStore | N/A |
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 files
isExcludedFromBackup - 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:
文件消失:
- 检查是否存储在Caches或tmp目录(系统可能已清理)
- 检查文件保护级别(设备锁定时可能无法访问)
- 技能参考:使用技能
axiom-storage-diag
备份过大:
- 检查是否将可重新下载的内容存在Documents目录(应存在Caches目录)
- 检查大型文件是否设置了
isExcludedFromBackup - 技能参考:使用技能
axiom-storage-management-ref
数据不同步:
- CloudKit:检查CKSyncEngine状态、账号可用性
- 技能参考:使用技能
axiom-cloud-sync-diag
- 技能参考:使用
- iCloud Drive:检查通用容器权限、文件协调器状态
- 技能参考:使用、
axiom-icloud-drive-ref技能axiom-cloud-sync-diag
- 技能参考:使用
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
当更换存储方案时:
数据库到数据库迁移(如Core Data → SwiftData):
- 创建与Core Data实体匹配的SwiftData模型
- 编写数据迁移代码
- 使用生产级别的数据集测试
- 保留旧数据库用于回滚
文件到数据库迁移:
- 识别所有存储结构化数据的JSON/plist文件
- 创建对应的SwiftData模型
- 在应用首次启动时执行一次性迁移
- 验证所有数据迁移完成后,删除旧文件
本地到云端迁移:
- 确保已配置正确的权限(CloudKit/iCloud)
- 谨慎处理初始上传(考虑带宽限制)
- 测试冲突解决逻辑
- 提供用户控制选项(如开启/关闭同步)
最后更新时间:2025-12-12
技能类型:指南类
相关WWDC会议:
- WWDC 2023-10187:SwiftData入门
- WWDC 2023-10188:使用CKSyncEngine同步到iCloud
- WWDC 2024-10137:SwiftData新特性