关于 axiom-storage
面向 iOS 开发者的存储决策指南,帮助按数据类型选择持久化格式、设备目录和云同步方案。它覆盖结构化数据、文件、本地存储与 CloudKit 或 iCloud Drive 的选择,并指向相关实现技能。
主要功能
- 01按数据形态选择存储格式
- 02比较 SwiftData、SQLite 与 Core Data
- 03选择 Documents、Caches 等目录
- 04判断 CloudKit 与 iCloud Drive 的适用场景
- 05提供存储迁移与故障排查指引
适用场景
- 01为新 iOS 项目规划数据架构
- 02选择结构化数据或文件的保存方式
- 03决定数据是否需要跨设备同步
- 04排查文件丢失、备份膨胀或同步异常
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-storage安装后在 Claude Code 中输入/axiom-storage确认已加载。
详细介绍
iOS 存储指南
目的:为所有存储决策提供导航中心——数据库还是文件、本地还是云端、具体存储位置 iOS 版本:iOS 17+(最新功能需 iOS 26+) 背景:完整的存储决策框架,整合 SwiftData(WWDC 2023)、CKSyncEngine(WWDC 2023)和文件管理最佳实践
何时使用此技能
✅ 在以下情况下使用此技能:
- 开始新项目并选择存储方式
- 询问“我应该把这些数据存在哪里?”
- 在 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-sqlitedata、axiom-grdb - 新增文件技能:
axiom-file-protection-ref、axiom-storage-management-ref、axiom-storage-diag - 新增云端技能:
axiom-cloudkit-ref、axiom-icloud-drive-ref、axiom-cloud-sync-diag
核心理念
“根据数据形态选择合适的工具,然后选择合适的位置。”
存储决策有两个维度:
- 格式:数据的结构是什么?(可查询的记录还是文件)
- 位置:数据存储在哪里?(本地还是云端,具体是哪个目录)
格式选错会迫使你采用变通方案。位置选错会导致数据丢失或备份膨胀。
完整决策树
第 1 层:格式——你要存储什么?
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
结构化数据路径
现代应用(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]
为什么选择 SwiftData:
- 现代原生 Swift API(无需 Objective-C)
- 类型安全的查询
- 内置 CloudKit 同步支持
- 可观察模型可与 SwiftUI 集成
- 使用技能:使用
axiom-swiftdata了解实现细节
不应使用 SwiftData 的情况:
- 需要高级 SQLite 功能(FTS5、复杂联接)
- 现有 Core Data 应用(迁移成本较高)
- 对性能要求极高(直接使用 SQLite 速度更快)
需要高级控制
// ✅ 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")
以下情况使用 SQLiteData:
- 需要全文搜索(FTS5)
- 需要自定义 SQL 查询和索引
- 追求最高性能(直接使用 SQLite)
- 从现有 SQLite 数据库迁移
- 使用技能:使用
axiom-sqlitedata了解现代 SQLite 模式
以下情况使用 GRDB:
- 需要响应式查询(ValueObservation)
- 需要执行复杂的数据库操作
- 需要类型安全的查询构建器
- 使用技能:使用
axiom-grdb了解高级模式
旧版应用(iOS 16 及更早版本)
// ❌ LEGACY: Core Data (avoid for new projects)
import CoreData
// NSManagedObject, NSFetchRequest, NSPredicate...
仅在以下情况下使用 Core Data:
- 维护现有的 Core Data 应用
- 无法将最低部署版本升级到 iOS 17
文件存储路径
文件决策树
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 目录
// ✅ 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)
}
关键规则:
- ✅ 应存储:用户创建的文档、导出的文件、用户可见内容
- ❌ 不应存储:可重新获取的下载数据、缓存、临时文件
- ⚠️ 警告:此目录中的所有内容都会备份到 iCloud。可重新下载的大型文件会使备份膨胀,还可能导致你的应用被拒绝。
使用技能:使用 axiom-file-protection-ref 了解加密选项
Application Support 目录
// ✅ 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
}
适用于:
- SwiftData/SQLite 数据库文件
- 用户偏好设置
- 必须持久保留的下载资源
- 配置文件
Caches 目录
// ✅ 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)
}
关键规则:
- ✅ 存储空间紧张时,系统可以且一定会删除此目录中的文件
- ✅ 始终确保可以重新下载或重新生成
- ❌ 不要存储无法重新创建的内容
使用技能:使用 axiom-storage-management-ref 了解清理策略和磁盘空间管理
临时目录
// ✅ 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)
}
主要规则:
- 系统可能随时删除此处的文件(即使应用正在运行)
- 请务必清理自己创建的文件
- 不要依赖文件在应用启动之间保留
云存储决策
是否应将数据同步到云端?
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 路径(结构化数据同步)
// ✅ CORRECT: SwiftData with CloudKit sync (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 的现代替代方案
- 使用技能:查看
axiom-cloudkit-ref了解 CKSyncEngine 模式
-
原始 CloudKit API(旧版):
- CKContainer、CKDatabase、CKRecord
- 手动管理同步
- 仅在 CKSyncEngine 不适用时使用
- 使用技能:查看
axiom-cloudkit-ref了解原始 API 参考
iCloud Drive 路径(文件同步)
// ✅ 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)
}
适用 iCloud Drive 的场景:
- 需要同步的用户创建文档
- 基于文件的协作
- 简单的文件同步(类似 Dropbox)
使用技能:查看 axiom-icloud-drive-ref 了解实现详情
键值存储(少量偏好设置)
// ✅ CORRECT: Small synced preferences
let store = NSUbiquitousKeyValueStore.default
store.set(true, forKey: "darkModeEnabled")
store.set(2.0, forKey: "textSize")
store.synchronize()
限制:
- 总存储空间最多 1 MB
- 最多 1024 个键
- 每个值最多 1 MB
- 仅用于偏好设置,不用于数据存储
常见模式与反模式
✅ 应做:根据数据形态选择
// ✅ 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"))
❌ 不应做:将结构化数据存储为文件
// ❌ 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 { ... }
❌ 不应做:将可重新下载的内容存储在 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)
}
❌ 不应做:使用 CloudKit 进行简单的文件同步
// ❌ 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"))
快速参考表
| 数据类型 | 格式 | 本地位置 | 云同步 | 使用技能 |
|---|---|---|---|---|
| 用户任务、笔记 | 结构化 | Application Support | SwiftData + CloudKit | axiom-swiftdata → axiom-cloudkit-ref |
| 用户照片(创建) | 文件 | Documents | iCloud Drive | axiom-file-protection-ref → axiom-icloud-drive-ref |
| 下载的图像 | 文件 | Caches | 无(重新下载) | axiom-storage-management-ref |
| 缩略图 | 文件 | Caches | 无(重新生成) | axiom-storage-management-ref |
| 数据库文件 | 文件 | Application Support | CKSyncEngine(自定义时) | axiom-sqlitedata → axiom-cloudkit-ref |
| 临时处理 | 文件 | tmp | 无 | 不适用 |
| 用户设置 | 键值 | UserDefaults | NSUbiquitousKeyValueStore | 不适用 |
tvOS 存储
tvOS 没有持久化的本地存储。 这会让每位 iOS 开发者措手不及。
| 目录 | tvOS 行为 |
|---|---|
| Documents | 不存在 |
| Application Support | 应用未运行时,系统可能会删除 |
| Caches | 系统随时会删除 |
| tmp | 系统随时会删除 |
| UserDefaults | 限制为 500 KB(iOS 上约为 4 MB) |
每个本地文件都可能在应用启动之间消失。 tvOS 应用必须能够从零开始运行。
建议:使用 iCloud(CloudKit、NSUbiquitousKeyValueStore 或 iCloud Drive)作为主要存储。仅将本地文件视为缓存。查看 axiom-tvos 了解完整的 tvOS 存储模式。
调试:数据丢失或未同步?
文件消失:
- 检查文件是否存储在 Caches 或 tmp 中(系统已清理这些文件)
- 检查文件保护级别(设备锁定时可能无法访问)
- 使用技能:
axiom-storage-diag
备份过大:
- 检查可重新下载的内容是否存放在 Documents 中(应存放在 Caches 中)
- 检查大型文件是否设置了
isExcludedFromBackup - 使用技能:
axiom-storage-management-ref
数据未同步:
- CloudKit:检查 CKSyncEngine 状态及账户可用性
- 使用技能:
axiom-cloud-sync-diag
- 使用技能:
- iCloud Drive:检查 ubiquitous 容器权限及文件协调器
- 使用技能:
axiom-icloud-drive-ref、axiom-cloud-sync-diag
- 使用技能:
迁移检查清单
更改存储方式时:
数据库到数据库(例如,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 新功能