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
File Protection Quick Reference
Set encryption level per file. See axiom-file-protection-ref for full guide.
Level When Accessible Use For
.complete
Only while unlocked Passwords, tokens, health data
.completeUnlessOpen
After first unlock if already open Active downloads, media recording
.completeUntilFirstUserAuthentication
After first unlock (default) Most app data
.none
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
-
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)