axiom-storage-management-ref について
iOS 開発者向けのストレージ管理リファレンススキルです。容量の確認、ファイルの削除優先度、バックアップ除外を扱い、保存判断に使う容量 API を解説します。
主な機能
- 01空き容量とファイル使用量の確認
- 02保存場所ごとの削除優先度の把握
- 03ファイルを削除候補に設定
- 04再取得可能なファイルのバックアップ除外
- 05ストレージ圧迫の監視とキャッシュ削除
活用シーン
- 01重要データや任意データの保存前に空き容量を確認
- 02キャッシュや一時ファイルの保存場所を選択
- 03ダウンロード済みメディアをデバイスのバックアップ対象外にする
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-storage-management-refインストール後、Claude Code で/axiom-storage-management-refを入力して読み込まれたか確認します。
詳細
iOS ストレージ管理リファレンス
目的: ストレージ不足、パージポリシー、ディスク容量、URL リソース値に関する包括的なリファレンス 利用可能環境: iOS 5.0 以降(基本機能)、iOS 11.0 以降(最新の容量 API) コンテキスト: 「iOS には、ファイルを『最後の手段としてパージ』するように指定する方法はありますか?」への回答
このスキルを使用する場面
次のことが必要な場合に、このスキルを使用してください。
- iOS のファイルパージ動作を理解する
- 利用可能なディスク容量を正しく確認する
- キャッシュファイルのパージ優先度を設定する
- ファイルをバックアップ対象から除外する
- ストレージ不足を監視する
- ファイルをパージ可能として指定する
- ボリューム容量 API を理解する
- 「ストレージ不足」の状況に対処する
中心となる質問
「iOS には、ファイルを『最後の手段としてパージ』するように指定する方法はありますか?」
回答: 直接指定することはできませんが、iOS には 2 つの方法があります。
-
保存場所に基づくパージ(暗黙の優先度):
tmp/→ 積極的にパージされます(いつでも)Library/Caches/→ ストレージ不足時にパージされますDocuments/、Application Support/→ パージされません
-
容量の確認(明示的な戦略):
volumeAvailableCapacityForImportantUsage— 保存必須のデータ向けvolumeAvailableCapacityForOpportunisticUsage— あれば望ましいデータ向け- 保存前に確認し、利用可能な容量に応じて保存場所を選びます
ストレージ用 URL リソース値
完全なリファレンステーブル
| リソースキー | 型 | 目的 | 利用可能環境 |
|---|---|---|---|
volumeAvailableCapacityKey | Int64 | 利用可能な合計容量 | iOS 5.0 以降 |
volumeAvailableCapacityForImportantUsageKey | Int64 | 必須ファイル用の容量 | iOS 11.0 以降 |
volumeAvailableCapacityForOpportunisticUsageKey | Int64 | 任意ファイル用の容量 | iOS 11.0 以降 |
volumeTotalCapacityKey | Int64 | ボリュームの合計容量 | iOS 5.0 以降 |
isExcludedFromBackupKey | Bool | iCloud/iTunes バックアップから除外 | iOS 5.1 以降 |
isPurgeableKey | Bool | ストレージ不足時にシステムが削除可能 | iOS 9.0 以降 |
fileAllocatedSizeKey | Int64 | 実際に使用しているディスク容量 | iOS 5.0 以降 |
totalFileAllocatedSizeKey | Int64 | メタデータを含む割り当て済み容量の合計 | iOS 5.0 以降 |
利用可能な容量の確認(最新の方法)
// ✅ CORRECT: Check appropriate capacity before saving
func checkSpaceBeforeSaving(fileSize: Int64, isEssential: Bool) -> Bool {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
do {
let values = try homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityForImportantUsageKey,
.volumeAvailableCapacityForOpportunisticUsageKey
])
if isEssential {
// For must-save data (user-created content, critical app data)
let importantCapacity = values.volumeAvailableCapacityForImportantUsage ?? 0
return fileSize < importantCapacity
} else {
// For nice-to-have data (caches, thumbnails)
let opportunisticCapacity = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
return fileSize < opportunisticCapacity
}
} catch {
print("Error checking capacity: \(error)")
return false
}
}
// Usage
if checkSpaceBeforeSaving(fileSize: imageData.count, isEssential: true) {
try imageData.write(to: documentsURL.appendingPathComponent("photo.jpg"))
} else {
showLowStorageAlert()
}
必須容量と任意容量の違い
volumeAvailableCapacityForImportantUsage:
- 必須の操作用に確保された容量
- 用途: ユーザーが作成したコンテンツ、保存必須のデータ
- システムはこの容量をより積極的に確保します
- しきい値は高めです
volumeAvailableCapacityForOpportunisticUsage:
- 任意の操作に利用できる容量
- 用途: キャッシュ、サムネイル、先読み
- しきい値は低めです(システムがすでにストレージ不足になっている可能性があります)
- 「必要なら保存してもよいが、システムの空き容量が少なくなっている」という状態を示します
// ✅ CORRECT: Different thresholds for different data types
func shouldDownloadThumbnail(size: Int64) -> Bool {
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
.resourceValues(forKeys: [.volumeAvailableCapacityForOpportunisticUsageKey])
.volumeAvailableCapacityForOpportunisticUsage ?? 0
// Only download optional content if there's plenty of space
return size < capacity
}
func canSaveUserDocument(size: Int64) -> Bool {
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
.volumeAvailableCapacityForImportantUsage ?? 0
// User documents are essential
return size < capacity
}
バックアップからの除外
isExcludedFromBackup
Caches/ 内のファイルは自動的にバックアップから除外されますが、ほかのディレクトリにある再ダウンロード可能なファイルにも、明示的に設定してください。
// ✅ CORRECT: Exclude large re-downloadable files from backup
func markExcludedFromBackup(url: URL) throws {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)
}
// Example: Downloaded podcast episodes
func downloadPodcast(url: URL) throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
// Download file
let data = try Data(contentsOf: url)
try data.write(to: podcastURL)
// Mark as excluded from backup (can re-download)
try markExcludedFromBackup(url: podcastURL)
}
バックアップから除外する対象:
- ✅ 再取得できるダウンロード済みコンテンツ
- ✅ 生成されたサムネイル
- ✅ キャッシュされた API レスポンス
- ✅ サーバーから取得した大容量メディアファイル
- ❌ ユーザーが作成したコンテンツ(常にバックアップする)
- ❌ 再生成できないアプリデータ
バックアップ状態の確認
// ✅ Check if file is excluded from backup
func isExcludedFromBackup(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.isExcludedFromBackupKey])
return values?.isExcludedFromBackup ?? false
}
パージ可能なファイル
isPurgeable
システムによる自動パージの候補としてファイルを指定します。
// ✅ CORRECT: Mark cache files as purgeable
func markAsPurgeable(url: URL) throws {
var resourceValues = URLResourceValues()
resourceValues.isPurgeable = true
try url.setResourceValues(resourceValues)
}
// Example: Thumbnail cache
func cacheThumbnail(image: UIImage, for url: URL) throws {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let thumbnailURL = cacheURL.appendingPathComponent(url.lastPathComponent)
// Save thumbnail
try image.pngData()?.write(to: thumbnailURL)
// Mark as purgeable
try markAsPurgeable(url: thumbnailURL)
// Also exclude from backup
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try thumbnailURL.setResourceValues(resourceValues)
}
注: Caches/ 内のファイルは、保存場所によってすでにパージ可能です。isPurgeable の設定は、ほかの場所にあるファイルに対する助言となります。
暗黙のパージ優先度(保存場所に基づく)
iOS は明示的な優先度フラグではなく、保存場所に基づいてファイルをパージします。
パージ優先度の階層
PURGED FIRST (Aggressive):
└── tmp/
- Purged: Anytime (even while app running)
- Lifetime: Hours to days
- Use for: Truly temporary intermediates
PURGED SECOND (Storage Pressure):
└── Library/Caches/
- Purged: When system needs space
- Lifetime: Weeks to months (if space available)
- Use for: Re-downloadable, regenerable content
NEVER PURGED (Permanent):
├── Documents/
│ - Backed up: ✅ Yes
│ - Purged: ❌ Never (unless app deleted)
│ - Use for: User-created content
│
└── Library/Application Support/
- Backed up: ✅ Yes
- Purged: ❌ Never (unless app deleted)
- Use for: Essential app data
実装戦略
// ✅ CORRECT: Choose location based on purge priority needs
func saveFile(data: Data, priority: FilePriority) throws {
let url: URL
switch priority {
case .essential:
// Never purged - for user-created or critical app data
url = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("important.dat")
case .cacheable:
// Purged under storage pressure - for re-downloadable content
url = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0].appendingPathComponent("cache.dat")
case .temporary:
// Purged aggressively - for temp files
url = FileManager.default.temporaryDirectory
.appendingPathComponent("temp.dat")
}
try data.write(to: url)
// For cacheable files, mark excluded from backup
if priority == .cacheable {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)
}
}
enum FilePriority {
case essential // Never purge
case cacheable // Purge under pressure
case temporary // Purge aggressively
}
ストレージ不足の検出
ストレージ不足への対応
// ✅ CORRECT: Monitor for low storage and clean up proactively
class StorageMonitor {
func checkStorageAndCleanup() {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
guard let values = try? homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityForOpportunisticUsageKey,
.volumeTotalCapacityKey
]) else { return }
let availableSpace = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
let totalSpace = values.volumeTotalCapacity ?? 1
// Calculate percentage
let percentAvailable = Double(availableSpace) / Double(totalSpace)
if percentAvailable < 0.10 { // Less than 10% free
print("⚠️ Low storage detected, cleaning up...")
cleanupCaches()
}
}
func cleanupCaches() {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
// Delete old cache files
let fileManager = FileManager.default
guard let files = try? fileManager.contentsOfDirectory(
at: cacheURL,
includingPropertiesForKeys: [.contentModificationDateKey]
) else { return }
// Sort by modification date
let sortedFiles = files.sorted { url1, url2 in
let date1 = (try? url1.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
let date2 = (try? url2.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
return (date1 ?? .distantPast) < (date2 ?? .distantPast)
}
// Delete oldest files first
for fileURL in sortedFiles.prefix(100) {
try? fileManager.removeItem(at: fileURL)
}
}
}
バックグラウンドクリーンアップタスク
// ✅ CORRECT: Register background task to clean up storage
import BackgroundTasks
func registerBackgroundCleanup() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.cleanup",
using: nil
) { task in
self.handleStorageCleanup(task: task as! BGProcessingTask)
}
}
func handleStorageCleanup(task: BGProcessingTask) {
task.expirationHandler = {
task.setTaskCompleted(success: false)
}
// Clean up old caches
cleanupOldFiles()
task.setTaskCompleted(success: true)
}
ファイルサイズの計算
正確なファイルサイズの取得
// ✅ CORRECT: Get actual disk usage (includes filesystem overhead)
func getFileSize(url: URL) -> Int64? {
let values = try? url.resourceValues(forKeys: [
.fileAllocatedSizeKey,
.totalFileAllocatedSizeKey
])
// Use totalFileAllocatedSize for accurate disk usage
return values?.totalFileAllocatedSize.map { Int64($0) }
}
// ✅ Calculate directory size
func getDirectorySize(url: URL) -> Int64 {
guard let enumerator = FileManager.default.enumerator(
at: url,
includingPropertiesForKeys: [.totalFileAllocatedSizeKey]
) else { return 0 }
var totalSize: Int64 = 0
for case let fileURL as URL in enumerator {
if let size = getFileSize(url: fileURL) {
totalSize += size
}
}
return totalSize
}
// Usage
let cacheSize = getDirectorySize(url: cachesDirectory)
print("Cache using \(cacheSize / 1_000_000) MB")
よくあるパターン
パターン 1: 利用可能な容量に基づいたスマートダウンロード
// ✅ CORRECT: Only download optional content if space available
func downloadOptionalContent(url: URL, size: Int64) async throws {
// Check opportunistic capacity
let homeURL = FileManager.default.homeDirectoryForCurrentUser
let values = try homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityForOpportunisticUsageKey
])
guard let available = values.volumeAvailableCapacityForOpportunisticUsage,
size < available else {
print("Skipping download - low storage")
return
}
// Proceed with download
let data = try await URLSession.shared.data(from: url).0
try data.write(to: cachesDirectory.appendingPathComponent(url.lastPathComponent))
}
パターン 2: 段階的なキャッシュクリーンアップ
// ✅ CORRECT: Clean up caches when approaching storage limits
class CacheManager {
func addToCache(data: Data, key: String) throws {
let cacheURL = getCacheURL(for: key)
// Check if we should clean up first
if shouldCleanupCache(addingSize: Int64(data.count)) {
cleanupOldestFiles(targetSize: 100 * 1_000_000) // 100 MB
}
try data.write(to: cacheURL)
}
func shouldCleanupCache(addingSize: Int64) -> Bool {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
guard let values = try? homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityForOpportunisticUsageKey
]) else { return false }
let available = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
// Clean up if less than 200 MB free
return available < 200 * 1_000_000
}
func cleanupOldestFiles(targetSize: Int64) {
// Delete oldest cache files until under target
// (implementation similar to earlier example)
}
}
パターン 3:ダウンロードしたメディアをバックアップから除外する
// ✅ CORRECT: Downloaded podcast/video management
class MediaDownloader {
func downloadMedia(url: URL) async throws {
let data = try await URLSession.shared.data(from: url).0
// Store in Application Support (not Caches, so it persists)
let mediaURL = applicationSupportDirectory
.appendingPathComponent("Downloads")
.appendingPathComponent(url.lastPathComponent)
try data.write(to: mediaURL)
// But exclude from backup (can re-download)
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try mediaURL.setResourceValues(resourceValues)
}
}
ストレージの問題をデバッグする
バックアップサイズを監査する
// ✅ Check what's being backed up
func auditBackupSize() {
let documentsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let size = getDirectorySize(url: documentsURL)
print("Documents (backed up): \(size / 1_000_000) MB")
// Check for large files that should be excluded
if size > 100 * 1_000_000 { // > 100 MB
print("⚠️ Large backup size - check for re-downloadable files")
findLargeFiles(in: documentsURL)
}
}
func findLargeFiles(in directory: URL) {
guard let enumerator = FileManager.default.enumerator(
at: directory,
includingPropertiesForKeys: [.totalFileAllocatedSizeKey]
) else { return }
for case let fileURL as URL in enumerator {
if let size = getFileSize(url: fileURL),
size > 10 * 1_000_000 { // > 10 MB
print("Large file: \(fileURL.lastPathComponent) (\(size / 1_000_000) MB)")
// Check if excluded from backup
if !isExcludedFromBackup(url: fileURL) {
print("⚠️ Should this be excluded from backup?")
}
}
}
}
クイックリファレンス
| タスク | API | コード |
|---|---|---|
| 必須ファイル用の空き容量を確認する | volumeAvailableCapacityForImportantUsageKey | values.volumeAvailableCapacityForImportantUsage |
| キャッシュ用の空き容量を確認する | volumeAvailableCapacityForOpportunisticUsageKey | values.volumeAvailableCapacityForOpportunisticUsage |
| バックアップから除外する | isExcludedFromBackupKey | resourceValues.isExcludedFromBackup = true |
| パージ可能としてマークする | isPurgeableKey | resourceValues.isPurgeable = true |
| ファイルサイズを取得する | totalFileAllocatedSizeKey | values.totalFileAllocatedSize |
| パージの優先順位 | 場所に基づく | tmp/ または Caches/ ディレクトリを使用する |
ファイル保護クイックリファレンス
ファイルごとに暗号化レベルを設定します。詳しいガイドは axiom-file-protection-ref を参照してください。
| レベル | アクセス可能なタイミング | 用途 |
|---|---|---|
.complete | ロック解除中のみ | パスワード、トークン、ヘルスデータ |
.completeUnlessOpen | すでに開いている場合、最初のロック解除後 | ダウンロード中のファイル、メディア録画 |
.completeUntilFirstUserAuthentication | 最初のロック解除後(デフォルト) | ほとんどのアプリデータ |
.none | ロック解除前も含め、常時 | バックグラウンド取得データ、プッシュペイロード |
// Set protection on file
try data.write(to: url, options: .completeFileProtection)
// Set protection on directory
try FileManager.default.createDirectory(
at: url,
withIntermediateDirectories: true,
attributes: [.protectionKey: FileProtectionType.complete]
)
// Check current protection
let values = try url.resourceValues(forKeys: [.fileProtectionKey])
print("Protection: \(values.fileProtection ?? .none)")
関連スキル
axiom-storage— ファイルの保存場所を決めるaxiom-file-protection-ref— ファイルの暗号化とセキュリティaxiom-storage-diag— ストレージ関連の問題をデバッグする
最終更新日:2025-12-12 スキルタイプ:リファレンス 最小 iOS バージョン:5.0(基本機能)、11.0(最新の容量 API)