axiom-storage について
iOS開発者向けのストレージ選択ガイドです。データの種類に応じて永続化形式、保存先、クラウド同期方式を選ぶ方法を示し、実装関連のスキルも案内します。
主な機能
- 01データの形に応じた保存形式の選択
- 02SwiftData、SQLite、Core Dataの比較
- 03DocumentsやCachesなど保存先の選定
- 04CloudKitと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
基本理念
「データの形に合ったツールを選び、それから適切な保存場所を選びます。」
ストレージの判断には、次の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 データベースから移行する場合
- スキルを使う: モダンな SQLite のパターンについては
axiom-sqlitedata
次の場合は 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 を使う 3 つの方法:
-
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 の方法(ファイル同期)
// ✅ 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)をプライマリストレージとして使用してください。ローカルファイルはキャッシュとしてのみ扱ってください。tvOS のストレージパターンの全容については axiom-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 の新機能