mcpField
A
axiom-storage-diag

axiom-storage-diag

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

axiom-storage-diag について

ファイルの消失、アクセス不能、バックアップ容量の異常を調べる iOS 開発者向けの診断スキルです。保存場所、ファイル保護設定、バックアップ除外の観点から原因を確認し、対処パターンを示します。

主な機能

  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-diag

インストール後、Claude Code で/axiom-storage-diagを入力して読み込まれたか確認します。

詳細

ローカルファイルストレージの診断

概要

基本原則 ファイルストレージの問題の90%は、ストレージの保存場所の選択ミス、ファイル保護レベルの誤解、またはバックアップ対象からの除外漏れが原因です。iOSのファイルシステムのバグではありません。

iOSのファイルシステムは、数百万のアプリやデバイスで実績を重ねてきました。ファイルが消える、アクセスできなくなる、またはバックアップの問題が発生する場合、ほとんどの場合、原因はストレージの保存場所の選択または保護設定にあります。

要注意事項 — ファイルストレージの問題を疑う

次のいずれかに該当する場合:

  • デバイスの再起動後にファイルが不可解に消える
  • ファイルがランダムに消える(作成から数週間後)
  • アプリのバックアップサイズが予想外に大きい(>500 MB)
  • アプリのバックグラウンド/フォアグラウンド切り替え後に「ファイルが見つかりません」と表示される
  • デバイスがロックされているとファイルにアクセスできない
  • iOSのアップデート後にデータを失ったとユーザーから報告がある
  • バックグラウンドタスクからファイルにアクセスできない

❌ 禁止 「iOSがファイルを削除した。ファイルシステムが壊れている」

  • iOSのファイルシステムは、すべてのアプリで毎日数十億のファイルを処理しています
  • システムの動作は文書化されており、予測可能です
  • 問題の99%は、保存場所と保護設定の不一致が原因です

必須の最初の手順

コードを変更する前に、必ず最初に次の項目を確認してください:

// 1. Check WHERE file is stored
func diagnoseFileLocation(_ url: URL) {
    let path = url.path
    if path.contains("/tmp/") {
        print("⚠️ File in tmp/ - system purges aggressively")
    } else if path.contains("/Caches/") {
        print("⚠️ File in Caches/ - purged under storage pressure")
    } else if path.contains("/Documents/") {
        print("✅ File in Documents/ - never purged, backed up")
    } else if path.contains("/Library/Application Support/") {
        print("✅ File in Application Support/ - never purged, backed up")
    }
}

// 2. Check file protection level
func diagnoseFileProtection(_ url: URL) throws {
    let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
    if let protection = attrs[.protectionKey] as? FileProtectionType {
        print("Protection: \(protection)")
        if protection == .complete {
            print("⚠️ File inaccessible when device locked")
        }
    }
}

// 3. Check backup status
func diagnoseBackupStatus(_ url: URL) throws {
    let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
    if let excluded = values.isExcludedFromBackup {
        print("Excluded from backup: \(excluded)")
    }
}

// 4. Check file existence and size
func diagnoseFileState(_ url: URL) {
    if FileManager.default.fileExists(atPath: url.path) {
        if let size = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int64 {
            print("File exists, size: \(size) bytes")
        }
    } else {
        print("❌ File does not exist")
    }
}

判断フロー

ファイルが消えた場合
Files missing? → Check where stored

├─ Disappeared after device restart
│   ├─ Was in tmp/? → EXPECTED (tmp/ purged on reboot)
│   │   → FIX: Move to Caches/ or Application Support/
│   │
│   ├─ Was in Caches/? → System purged (storage pressure)
│   │   → FIX: Move to Application Support/ if can't be regenerated
│   │
│   └─ Protection level .complete? → Inaccessible until unlock
│       → FIX: Wait for unlock or use .completeUntilFirstUserAuthentication
│
├─ Disappeared randomly (weeks later)
│   ├─ In Caches/? → System purged under storage pressure
│   │   → EXPECTED if re-downloadable
│   │   → FIX: Re-download when needed, or move to Application Support/
│   │
│   └─ In Documents or Application Support/?
│       → Check if user deleted app (purges all data)
│       → Check iOS update (rare, but check migration path)
│
└─ Only some files missing
    → Check isExcludedFromBackup + iCloud sync
    → Check if file names have special characters
    → Check file permissions
ファイルにアクセスできない場合
Can't access file?

├─ Error: "No permission" or NSFileReadNoPermissionError
│   ├─ Device locked? → Check file protection
│   │   └─ .complete protection? → Wait for unlock
│   │       → FIX: Use .completeUntilFirstUserAuthentication
│   │
│   └─ Background task accessing? → .complete blocks background
│       → FIX: Change to .completeUntilFirstUserAuthentication
│
├─ File exists but read returns empty/nil
│   └─ Check actual file size on disk
│       → May be zero-byte file from failed write
│
└─ File exists in debugger but not at runtime
    → Check if using wrong directory (Documents vs Caches)
    → Check URL construction
バックアップサイズが大きすぎる場合
App backup > 500 MB?

├─ Check Documents directory size
│   └─ Large files (>10 MB each)?
│       ├─ Can they be re-downloaded? → Move to Caches + isExcludedFromBackup
│       └─ User-created? → Keep in Documents (warn user if >1 GB)
│
├─ Check Application Support size
│   └─ Downloaded media/podcasts?
│       → Mark isExcludedFromBackup = true
│
└─ Audit backup with code:
    ```swift
    func auditBackupSize() {
        let docsURL = FileManager.default.urls(
            for: .documentDirectory,
            in: .userDomainMask
        )[0]
        let size = getDirectorySize(url: docsURL)
        print("Documents (backed up): \(size / 1_000_000) MB")
    }
    ```

症状ごとの一般的なパターン

パターン1:tmp/ 内のファイルが消える

症状:再起動後、またはアプリのライフサイクル中に一時ファイルが見つからない

原因:tmp/ はシステムによって積極的に消去されます

修正:

// ❌ WRONG: Using tmp/ for anything that should persist
let tmpURL = FileManager.default.temporaryDirectory
let fileURL = tmpURL.appendingPathComponent("data.json")
try data.write(to: fileURL)  // WILL BE DELETED

// ✅ CORRECT: Use Caches/ for re-generable data
let cacheURL = FileManager.default.urls(
    for: .cachesDirectory,
    in: .userDomainMask
)[0]
let fileURL = cacheURL.appendingPathComponent("data.json")
try data.write(to: fileURL)
パターン2:キャッシュが消去され、データが失われる

症状:ダウンロードしたコンテンツが数週間後に消える

原因:ストレージの空き容量が少なくなると、Caches/ は消去されます(想定された動作です)

修正:必要に応じて再ダウンロードするか、再生成できない場合はApplication Supportに移動してください

// ✅ CORRECT: Handle missing cache gracefully
func loadCachedImage(url: URL) async throws -> UIImage {
    let cacheURL = getCacheURL(for: url)

    // Try cache first
    if FileManager.default.fileExists(atPath: cacheURL.path),
       let data = try? Data(contentsOf: cacheURL),
       let image = UIImage(data: data) {
        return image
    }

    // Cache miss - re-download
    let (data, _) = try await URLSession.shared.data(from: url)
    try data.write(to: cacheURL)
    return UIImage(data: data)!
}
パターン3:.complete の保護によってバックグラウンド処理が妨げられる

症状:バックグラウンドタスクが「アクセス許可がありません」というエラーで失敗する

原因:.complete の保護が設定されたファイルは、デバイスのロック中にアクセスできません

修正:

// ❌ WRONG: .complete protection for background-accessed files
try data.write(to: url, options: .completeFileProtection)
// Background task fails when device locked

// ✅ CORRECT: Use .completeUntilFirstUserAuthentication
try data.write(
    to: url,
    options: .completeFileProtectionUntilFirstUserAuthentication
)
// Accessible in background after first unlock
パターン4:ダウンロードしたコンテンツによるバックアップの肥大化

症状:アプリのバックアップが1 GBを超え、アプリが却下される、またはユーザーから苦情が寄せられる

原因:ダウンロードしたコンテンツがDocuments/に保存されているか、バックアップ対象から除外されていません

修正:

// ✅ CORRECT: Exclude re-downloadable content
func downloadPodcast(url: URL) async throws {
    let appSupportURL = FileManager.default.urls(
        for: .applicationSupportDirectory,
        in: .userDomainMask
    )[0]

    let podcastURL = appSupportURL
        .appendingPathComponent("Podcasts")
        .appendingPathComponent(url.lastPathComponent)

    // Download
    let (data, _) = try await URLSession.shared.data(from: url)
    try data.write(to: podcastURL)

    // Mark excluded from backup
    var resourceValues = URLResourceValues()
    resourceValues.isExcludedFromBackup = true
    try podcastURL.setResourceValues(resourceValues)
}

本番環境での重大な障害シナリオ

症状:iOSのアップデート後に写真が失われたとユーザーから報告がある

診断手順:

  1. ストレージの保存場所を確認する(5分):

    // Were photos in Caches/?
    let photosInCaches = path.contains("/Caches/")
    // If yes → system purged them (expected)
    
  2. バックアップ済みか確認する(5分):

    // Check if excluded from backup
    let excluded = try? url.resourceValues(
        forKeys: [.isExcludedFromBackupKey]
    ).isExcludedFromBackup
    // If excluded=true AND not synced → lost
    
  3. 移行経路を確認する(10分):

    • アプリコンテナのパスは変更されましたか?
    • 古い保存場所からデータを移行しましたか?

根本原因(ケースの90%):

  • 写真がCaches/に保存されている(ストレージの空き容量が少なくなると消去される)
  • 写真がバックアップ対象から除外されており、クラウド同期もない
  • 主要なiOSアップデート後の移行コードがない

修正:

  • ユーザーの写真は必ずDocuments/に保存してください
  • ユーザーが作成したコンテンツをバックアップ対象から除外しないでください
  • ユーザーのコンテンツは必ずクラウド同期またはバックアップで保護してください

クイック診断チェックリスト

ストレージに関する問題が発生したら、次の項目を確認してください:

func diagnoseStorageIssue(fileURL: URL) {
    print("=== Storage Diagnosis ===")

    // 1. Location
    diagnoseFileLocation(fileURL)

    // 2. Protection
    try? diagnoseFileProtection(fileURL)

    // 3. Backup status
    try? diagnoseBackupStatus(fileURL)

    // 4. File state
    diagnoseFileState(fileURL)

    // 5. Directory size
    if let parentURL = fileURL.deletingLastPathComponent() as URL? {
        let size = getDirectorySize(url: parentURL)
        print("Parent directory size: \(size / 1_000_000) MB")
    }

    print("=== End Diagnosis ===")
}

関連スキル

  • axiom-storage — 適切なストレージの保存場所を選択する
  • axiom-file-protection-ref — 保護レベルを理解する
  • axiom-storage-management-ref — 消去の動作と容量API

最終更新日:2025-12-12 スキルの種類:診断

コメント