axiom-storage

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iOS 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
    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
在以下场景使用本技能
  • 启动新项目并选择存储方案时
  • 询问“我应该把数据存在哪里?”时
  • 需要在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

Core Philosophy

核心原则

"Choose the right tool for your data shape. Then choose the right location."
Storage decisions have two dimensions:
  1. Format: How is data structured? (Queryable records vs files)
  2. 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.

“根据数据形态选择合适的工具,再选择合适的存储位置。”
存储决策包含两个维度:
  1. 格式:数据的结构是什么?(可查询记录 vs 文件)
  2. 位置:数据存在哪里?(本地 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:
    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)
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:
    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
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:
axiom-file-protection-ref
for encryption options
swift
// ✅ 正确做法:用户创建的内容存储在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-ref
技能

Application 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:
axiom-storage-management-ref
for purge policies and disk space management
swift
// ✅ 正确做法:可重新下载的内容存储在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-ref
技能

Temporary 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:
  1. SwiftData + CloudKit (Recommended, iOS 17+):
    • Automatic sync for SwiftData models
    • Private database only
    • Easiest approach
    • Use skill:
      axiom-swiftdata
      for details
  2. CKSyncEngine (Custom persistence, iOS 17+):
    • For SQLite, GRDB, or custom stores
    • Manages sync automatically
    • Modern replacement for manual CloudKit
    • Use skill:
      axiom-cloudkit-ref
      for CKSyncEngine patterns
  3. Raw CloudKit APIs (Legacy):
    • CKContainer, CKDatabase, CKRecord
    • Manual sync management
    • Only if CKSyncEngine doesn't fit
    • Use skill:
      axiom-cloudkit-ref
      for raw API reference
swift
// ✅ 正确做法:SwiftData结合CloudKit同步(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集成的现代化方案
    • 技能参考:CKSyncEngine使用模式请参考
      axiom-cloudkit-ref
      技能
  3. 原生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:
axiom-icloud-drive-ref
for implementation details
swift
// ✅ 正确做法:使用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-ref
技能

Key-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 TypeFormatLocal LocationCloud SyncUse Skill
User tasks, notesStructuredApplication SupportSwiftData + CloudKit
axiom-swiftdata
axiom-cloudkit-ref
User photos (created)FileDocumentsiCloud Drive
axiom-file-protection-ref
axiom-icloud-drive-ref
Downloaded imagesFileCachesNone (re-download)
axiom-storage-management-ref
ThumbnailsFileCachesNone (regenerate)
axiom-storage-management-ref
Database fileFileApplication SupportCKSyncEngine (if custom)
axiom-sqlitedata
axiom-cloudkit-ref
Temp processingFiletmpNoneN/A
User settingsKey-ValueUserDefaultsNSUbiquitousKeyValueStoreN/A

数据类型格式本地存储位置云端同步方案参考技能
用户任务、笔记结构化Application SupportSwiftData + CloudKit
axiom-swiftdata
axiom-cloudkit-ref
用户拍摄的照片文件DocumentsiCloud Drive
axiom-file-protection-ref
axiom-icloud-drive-ref
下载的图片文件Caches无(重新下载)
axiom-storage-management-ref
缩略图文件Caches无(重新生成)
axiom-storage-management-ref
数据库文件文件Application SupportCKSyncEngine(自定义存储时)
axiom-sqlitedata
axiom-cloudkit-ref
临时处理文件文件tmpN/A
用户设置键值对UserDefaultsNSUbiquitousKeyValueStoreN/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
    isExcludedFromBackup
    is set on large files
  • Use skill:
    axiom-storage-management-ref
Data not syncing:
  • CloudKit: Check CKSyncEngine status, account availability
    • Use skill:
      axiom-cloud-sync-diag
  • iCloud Drive: Check ubiquitous container entitlements, file coordinator
    • Use skill:
      axiom-icloud-drive-ref
      ,
      axiom-cloud-sync-diag

文件消失
  • 检查是否存储在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新特性