mcpField
A
axiom-storage

axiom-storage

github.com/charleswiltgen数据与数据库1.2k176 次安装今天更新
代码仓库

关于 axiom-storage

面向 iOS 开发者的存储决策指南,帮助按数据类型选择持久化格式、设备目录和云同步方案。它覆盖结构化数据、文件、本地存储与 CloudKit 或 iCloud Drive 的选择,并指向相关实现技能。

主要功能

  1. 01按数据形态选择存储格式
  2. 02比较 SwiftData、SQLite 与 Core Data
  3. 03选择 Documents、Caches 等目录
  4. 04判断 CloudKit 与 iCloud Drive 的适用场景
  5. 05提供存储迁移与故障排查指引

适用场景

  1. 01为新 iOS 项目规划数据架构
  2. 02选择结构化数据或文件的保存方式
  3. 03决定数据是否需要跨设备同步
  4. 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. 格式:数据的结构是什么?(可查询的记录还是文件)
  2. 位置:数据存储在哪里?(本地还是云端,具体是哪个目录)

格式选错会迫使你采用变通方案。位置选错会导致数据丢失或备份膨胀。


完整决策树

第 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 的三种方式:

  1. SwiftData + CloudKit(推荐,iOS 17+):

    • 自动同步 SwiftData 模型
    • 仅限私有数据库
    • 最简单的方式
    • 使用技能:查看 axiom-swiftdata 了解详情
  2. CKSyncEngine(自定义持久化,iOS 17+):

    • 适用于 SQLite、GRDB 或自定义存储
    • 自动管理同步
    • 手动 CloudKit 的现代替代方案
    • 使用技能:查看 axiom-cloudkit-ref 了解 CKSyncEngine 模式
  3. 原始 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 SupportSwiftData + CloudKitaxiom-swiftdata → axiom-cloudkit-ref
用户照片(创建)文件DocumentsiCloud Driveaxiom-file-protection-ref → axiom-icloud-drive-ref
下载的图像文件Caches无(重新下载)axiom-storage-management-ref
缩略图文件Caches无(重新生成)axiom-storage-management-ref
数据库文件文件Application SupportCKSyncEngine(自定义时)axiom-sqlitedata → axiom-cloudkit-ref
临时处理文件tmp无不适用
用户设置键值UserDefaultsNSUbiquitousKeyValueStore不适用

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 新功能

评论

支持 Markdown