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:
Location-based purging (implicit priority):
-
tmp/ → Purged aggressively (anytime)
-
Library/Caches/ → Purged under storage pressure
-
Documents/ , Application Support/ → Never purged
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)
// ✅ 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 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)