home / skills / charleswiltgen / axiom / axiom-storage-management-ref

This skill helps you manage iOS storage, purge policies, and URL resource values to optimize disk usage and prevent low storage.

npx playbooks add skill charleswiltgen/axiom --skill axiom-storage-management-ref

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
17.7 KB
---
name: axiom-storage-management-ref
description: Use when asking about 'purge files', 'storage pressure', 'disk space iOS', 'isExcludedFromBackup', 'URL resource values', 'volumeAvailableCapacity', 'low storage', 'file purging priority', 'cache management' - comprehensive reference for iOS storage management and URL resource value APIs
license: MIT
compatibility: iOS 5.0+, iPadOS 5.0+, macOS 10.7+
metadata:
  version: "1.0.0"
  last-updated: "2025-12-12"
---

# iOS Storage Management Reference

**Purpose**: Comprehensive reference for storage pressure, purging policies, disk space, and URL resource values
**Availability**: iOS 5.0+ (basic), iOS 11.0+ (modern capacity APIs)
**Context**: Answer to "Does iOS provide any way to mark files as 'purge as last resort'?"

## When to Use This Skill

Use this skill when you need to:
- Understand iOS file purging behavior
- Check available disk space correctly
- Set purge priorities for cached files
- Exclude files from backup
- Monitor storage pressure
- Mark files as purgeable
- Understand volume capacity APIs
- Handle "low storage" scenarios

## The Core Question

> **"Does iOS provide any way to mark files as 'purge as last resort'?"**

**Answer**: Not directly, but iOS provides two approaches:

1. **Location-based purging** (implicit priority):
   - `tmp/` → Purged aggressively (anytime)
   - `Library/Caches/` → Purged under storage pressure
   - `Documents/`, `Application Support/` → Never purged

2. **Capacity checking** (explicit strategy):
   - `volumeAvailableCapacityForImportantUsage` — For must-save data
   - `volumeAvailableCapacityForOpportunisticUsage` — For nice-to-have data
   - Check before saving, choose location based on available space

---

## URL Resource Values for Storage

### Complete Reference Table

| Resource Key | Type | Purpose | Availability |
|--------------|------|---------|--------------|
| `volumeAvailableCapacityKey` | Int64 | Total available space | iOS 5.0+ |
| `volumeAvailableCapacityForImportantUsageKey` | Int64 | Space for essential files | iOS 11.0+ |
| `volumeAvailableCapacityForOpportunisticUsageKey` | Int64 | Space for optional files | iOS 11.0+ |
| `volumeTotalCapacityKey` | Int64 | Total volume capacity | iOS 5.0+ |
| `isExcludedFromBackupKey` | Bool | Exclude from iCloud/iTunes backup | iOS 5.1+ |
| `isPurgeableKey` | Bool | System can delete under pressure | iOS 9.0+ |
| `fileAllocatedSizeKey` | Int64 | Actual disk space used | iOS 5.0+ |
| `totalFileAllocatedSizeKey` | Int64 | Total allocated (including metadata) | iOS 5.0+ |

### Checking Available Space (Modern Approach)

```swift
// ✅ 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()
}
```

### Important vs Opportunistic Capacity

**volumeAvailableCapacityForImportantUsage**:
- Space reserved for **essential** operations
- Use for: User-created content, must-save data
- System reserves this space more aggressively
- Higher threshold

**volumeAvailableCapacityForOpportunisticUsage**:
- Space available for **optional** operations
- Use for: Caches, thumbnails, pre-fetching
- Lower threshold (system may already be under pressure)
- Indicates "go ahead if you want, but system is getting full"

```swift
// ✅ 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
}
```

---

## Backup Exclusion

### isExcludedFromBackup

Files in `Caches/` are automatically excluded from backup, but you should **explicitly mark** re-downloadable files in other directories.

```swift
// ✅ 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)
}
```

**When to exclude from backup**:
- ✅ Downloaded content that can be re-fetched
- ✅ Generated thumbnails
- ✅ Cached API responses
- ✅ Large media files from server
- ❌ User-created content (always back up)
- ❌ App data that can't be recreated

### Checking Backup Status

```swift
// ✅ Check if file is excluded from backup
func isExcludedFromBackup(url: URL) -> Bool {
    let values = try? url.resourceValues(forKeys: [.isExcludedFromBackupKey])
    return values?.isExcludedFromBackup ?? false
}
```

---

## Purgeable Files

### isPurgeable

Mark files as candidates for automatic purging by the system.

```swift
// ✅ 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)
}
```

**Note**: Files in `Caches/` are already purgeable by location. Setting `isPurgeable` is advisory for files in other locations.

---

## Implicit Purge Priority (Location-Based)

iOS purges files based on **location**, not explicit priority flags.

### Purge Priority Hierarchy

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

### Implementation Strategy

```swift
// ✅ 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
}
```

---

## Storage Pressure Detection

### Responding to Low Storage

```swift
// ✅ 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)
        }
    }
}
```

### Background Cleanup Task

```swift
// ✅ 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)
}
```

---

## File Size Calculation

### Getting Accurate File Sizes

```swift
// ✅ 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")
```

---

## Common Patterns

### Pattern 1: Smart Download Based on Available Space

```swift
// ✅ 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))
}
```

### Pattern 2: Progressive Cache Cleanup

```swift
// ✅ 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)
    }
}
```

### Pattern 3: Exclude Downloaded Media from Backup

```swift
// ✅ 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)
    }
}
```

---

## Debugging Storage Issues

### Audit Backup Size

```swift
// ✅ 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?")
            }
        }
    }
}
```

---

## Quick Reference

| Task | API | Code |
|------|-----|------|
| Check space for essential file | `volumeAvailableCapacityForImportantUsageKey` | `values.volumeAvailableCapacityForImportantUsage` |
| Check space for cache | `volumeAvailableCapacityForOpportunisticUsageKey` | `values.volumeAvailableCapacityForOpportunisticUsage` |
| Exclude from backup | `isExcludedFromBackupKey` | `resourceValues.isExcludedFromBackup = true` |
| Mark purgeable | `isPurgeableKey` | `resourceValues.isPurgeable = true` |
| Get file size | `totalFileAllocatedSizeKey` | `values.totalFileAllocatedSize` |
| Purge priority | Location-based | Use `tmp/` or `Caches/` directory |

---

## Related Skills

- `axiom-storage` — Decide where to store files
- `axiom-file-protection-ref` — File encryption and security
- `axiom-storage-diag` — Debug storage-related issues

---

**Last Updated**: 2025-12-12
**Skill Type**: Reference
**Minimum iOS**: 5.0 (basic), 11.0 (modern capacity APIs)

Overview

This skill is a compact reference for iOS storage management and URL resource value APIs. It explains location-based purging, capacity checks, backup exclusion, purgeable flags, and practical patterns for detecting and responding to low storage. Use it to make informed choices about where and how to store files safely on xOS platforms.

How this skill works

The skill describes two complementary strategies: implicit location-based priorities (tmp, Caches, Documents/Application Support) and explicit capacity checks via URL resource keys (important vs opportunistic usage). It covers URLResourceValues keys such as volumeAvailableCapacityForImportantUsageKey, volumeAvailableCapacityForOpportunisticUsageKey, isExcludedFromBackupKey, and isPurgeableKey. It also includes patterns for measuring actual disk usage, marking files, and performing proactive cleanup or background tasks.

When to use it

  • Deciding where to save user documents vs caches.
  • Implementing smart downloads that check available space first.
  • Marking files to exclude from iCloud/iTunes backups.
  • Marking files as purgeable or placing them in Caches/tmp for system cleanup.
  • Detecting low storage and triggering cache cleanup or background clean tasks.

Best practices

  • Choose storage location based on lifetime: tmp for transient, Caches for re-downloadable, Documents/Application Support for permanent data.
  • Use volumeAvailableCapacityForImportantUsageKey for essential saves and volumeAvailableCapacityForOpportunisticUsageKey for optional content.
  • Explicitly set isExcludedFromBackup for large re-downloadable files outside Caches.
  • Mark nonessential files as isPurgeable when appropriate, but rely primarily on location for priority.
  • Monitor opportunistic capacity percentage and delete oldest cache items when free space falls below a threshold (e.g., 10%).
  • Use background processing tasks to perform periodic cleanup without blocking the app.

Example use cases

  • Before saving a user-generated photo, check important capacity and write to Documents if space permits.
  • Save thumbnails to Caches, mark as isPurgeable and isExcludedFromBackup so system can reclaim them under pressure.
  • Store temporary processing files in tmp/ so they can be removed anytime by the OS.
  • Implement a cache manager that calculates totalFileAllocatedSize and removes oldest items when available opportunistic capacity is low.
  • Register a BGProcessingTask to run periodic cache cleanup when the system grants background time.

FAQ

Can I mark a file as "never purge" explicitly?

No. iOS does not expose a direct "never purge" flag; use Documents/Application Support for permanent data and rely on backup behavior to protect it.

Should I trust isPurgeable instead of using Caches/tmp?

isPurgeable is advisory and useful for files outside standard locations, but placing transient files in Caches or tmp is the primary mechanism for implicit priority.