Skillshub axiom-storage-management-ref
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
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/CharlesWiltgen/Axiom/axiom-storage-management-ref" ~/.claude/skills/comeonoliver-skillshub-axiom-storage-management-ref && rm -rf "$T"
skills/CharlesWiltgen/Axiom/axiom-storage-management-ref/SKILL.mdiOS 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:
-
Location-based purging (implicit priority):
→ Purged aggressively (anytime)tmp/
→ Purged under storage pressureLibrary/Caches/
,Documents/
→ Never purgedApplication Support/
-
Capacity checking (explicit strategy):
— For must-save datavolumeAvailableCapacityForImportantUsage
— For nice-to-have datavolumeAvailableCapacityForOpportunisticUsage- Check before saving, choose location based on available space
URL Resource Values for Storage
Complete Reference Table
| Resource Key | Type | Purpose | Availability |
|---|---|---|---|
| Int64 | Total available space | iOS 5.0+ |
| Int64 | Space for essential files | iOS 11.0+ |
| Int64 | Space for optional files | iOS 11.0+ |
| Int64 | Total volume capacity | iOS 5.0+ |
| Bool | Exclude from iCloud/iTunes backup | iOS 5.1+ |
| Bool | System can delete under pressure | iOS 9.0+ |
| Int64 | Actual disk space used | iOS 5.0+ |
| Int64 | Total allocated (including metadata) | iOS 5.0+ |
Checking Available Space (Modern Approach)
// ✅ 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"
// ✅ 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.
// ✅ 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
// ✅ 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.
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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 | | |
| Check space for cache | | |
| Exclude from backup | | |
| Mark purgeable | | |
| Get file size | | |
| Purge priority | Location-based | Use or directory |
File Protection Quick Reference
Set encryption level per file. See
axiom-file-protection-ref for full guide.
| Level | When Accessible | Use For |
|---|---|---|
| Only while unlocked | Passwords, tokens, health data |
| After first unlock if already open | Active downloads, media recording |
| After first unlock (default) | Most app data |
| Always, even before unlock | Background fetch data, push payloads |
// 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)")
Related Skills
— Decide where to store filesaxiom-storage
— File encryption and securityaxiom-file-protection-ref
— Debug storage-related issuesaxiom-storage-diag
Last Updated: 2025-12-12 Skill Type: Reference Minimum iOS: 5.0 (basic), 11.0 (modern capacity APIs)