mcpField
A
axiom-storage-management-ref

axiom-storage-management-ref

github.com/charleswiltgen開発1.2k168 回インストール今日更新
リポジトリ

axiom-storage-management-ref について

iOS 開発者向けのストレージ管理リファレンススキルです。容量の確認、ファイルの削除優先度、バックアップ除外を扱い、保存判断に使う容量 API を解説します。

主な機能

  1. 01空き容量とファイル使用量の確認
  2. 02保存場所ごとの削除優先度の把握
  3. 03ファイルを削除候補に設定
  4. 04再取得可能なファイルのバックアップ除外
  5. 05ストレージ圧迫の監視とキャッシュ削除

活用シーン

  1. 01重要データや任意データの保存前に空き容量を確認
  2. 02キャッシュや一時ファイルの保存場所を選択
  3. 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 つの方法があります。

  1. 保存場所に基づくパージ(暗黙の優先度):

    • tmp/ → 積極的にパージされます(いつでも)
    • Library/Caches/ → ストレージ不足時にパージされます
    • Documents/、Application Support/ → パージされません
  2. 容量の確認(明示的な戦略):

    • volumeAvailableCapacityForImportantUsage — 保存必須のデータ向け
    • volumeAvailableCapacityForOpportunisticUsage — あれば望ましいデータ向け
    • 保存前に確認し、利用可能な容量に応じて保存場所を選びます

ストレージ用 URL リソース値

完全なリファレンステーブル
リソースキー型目的利用可能環境
volumeAvailableCapacityKeyInt64利用可能な合計容量iOS 5.0 以降
volumeAvailableCapacityForImportantUsageKeyInt64必須ファイル用の容量iOS 11.0 以降
volumeAvailableCapacityForOpportunisticUsageKeyInt64任意ファイル用の容量iOS 11.0 以降
volumeTotalCapacityKeyInt64ボリュームの合計容量iOS 5.0 以降
isExcludedFromBackupKeyBooliCloud/iTunes バックアップから除外iOS 5.1 以降
isPurgeableKeyBoolストレージ不足時にシステムが削除可能iOS 9.0 以降
fileAllocatedSizeKeyInt64実際に使用しているディスク容量iOS 5.0 以降
totalFileAllocatedSizeKeyInt64メタデータを含む割り当て済み容量の合計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コード
必須ファイル用の空き容量を確認するvolumeAvailableCapacityForImportantUsageKeyvalues.volumeAvailableCapacityForImportantUsage
キャッシュ用の空き容量を確認するvolumeAvailableCapacityForOpportunisticUsageKeyvalues.volumeAvailableCapacityForOpportunisticUsage
バックアップから除外するisExcludedFromBackupKeyresourceValues.isExcludedFromBackup = true
パージ可能としてマークするisPurgeableKeyresourceValues.isPurgeable = true
ファイルサイズを取得するtotalFileAllocatedSizeKeyvalues.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)

コメント