| 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:
-
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)
func checkSpaceBeforeSaving(fileSize: Int64, isEssential: Bool) -> Bool {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
do {
let values = try homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityForImportantUsageKey,
.volumeAvailableCapacityForOpportunisticUsageKey
])
if isEssential {
let importantCapacity = values.volumeAvailableCapacityForImportantUsage ?? 0
return fileSize < importantCapacity
} else {
let opportunisticCapacity = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
return fileSize < opportunisticCapacity
}
} catch {
print("Error checking capacity: \(error)")
return false
}
}
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"
func shouldDownloadThumbnail(size: Int64) -> Bool {
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
.resourceValues(forKeys: [.volumeAvailableCapacityForOpportunisticUsageKey])
.volumeAvailableCapacityForOpportunisticUsage ?? 0
return size < capacity
}
func canSaveUserDocument(size: Int64) -> Bool {
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
.volumeAvailableCapacityForImportantUsage ?? 0
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.
func markExcludedFromBackup(url: URL) throws {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)
}
func downloadPodcast(url: URL) throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
let data = try Data(contentsOf: url)
try data.write(to: podcastURL)
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
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.
func markAsPurgeable(url: URL) throws {
var resourceValues = URLResourceValues()
resourceValues.isPurgeable = true
try url.setResourceValues(resourceValues)
}
func cacheThumbnail(image: UIImage, for url: URL) throws {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let thumbnailURL = cacheURL.appendingPathComponent(url.lastPathComponent)
try image.pngData()?.write(to: thumbnailURL)
try markAsPurgeable(url: thumbnailURL)
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
func saveFile(data: Data, priority: FilePriority) throws {
let url: URL
switch priority {
case .essential:
url = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("important.dat")
case .cacheable:
url = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0].appendingPathComponent("cache.dat")
case .temporary:
url = FileManager.default.temporaryDirectory
.appendingPathComponent("temp.dat")
}
try data.write(to: url)
if priority == .cacheable {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)
}
}
enum FilePriority {
case essential
cacheable
temporary
}
Storage Pressure Detection
Responding to Low Storage
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
let percentAvailable = Double(availableSpace) / Double(totalSpace)
if percentAvailable < 0.10 {
print("⚠️ Low storage detected, cleaning up...")
cleanupCaches()
}
}
func cleanupCaches() {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let fileManager = FileManager.default
files fileManager.contentsOfDirectory(
at: cacheURL,
includingPropertiesForKeys: [.contentModificationDateKey]
) { }
sortedFiles files.sorted { url1, url2
date1 ( url1.resourceValues(forKeys: [.contentModificationDateKey])).contentModificationDate
date2 ( url2.resourceValues(forKeys: [.contentModificationDateKey])).contentModificationDate
(date1 .distantPast) (date2 .distantPast)
}
fileURL sortedFiles.prefix() {
fileManager.removeItem(at: fileURL)
}
}
}
Background Cleanup Task
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)
}
cleanupOldFiles()
task.setTaskCompleted(success: true)
}
File Size Calculation
Getting Accurate File Sizes
func getFileSize(url: URL) -> Int64? {
let values = try? url.resourceValues(forKeys: [
.fileAllocatedSizeKey,
.totalFileAllocatedSizeKey
])
return values?.totalFileAllocatedSize.map { Int64($0) }
}
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
}
let cacheSize = getDirectorySize(url: cachesDirectory)
print("Cache using \(cacheSize ) MB")
Common Patterns
Pattern 1: Smart Download Based on Available Space
func downloadOptionalContent(url: URL, size: Int64) async throws {
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
}
let data = try await URLSession.shared.data(from: url).0
try data.write(to: cachesDirectory.appendingPathComponent(url.lastPathComponent))
}
Pattern 2: Progressive Cache Cleanup
class CacheManager {
func addToCache(data: Data, key: String) throws {
let cacheURL = getCacheURL(for: key)
if shouldCleanupCache(addingSize: Int64(data.count)) {
cleanupOldestFiles(targetSize: 100 * 1_000_000)
}
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
return available < 200 * 1_000_000
}
func cleanupOldestFiles(: ) {
}
}
Pattern 3: Exclude Downloaded Media from Backup
class MediaDownloader {
func downloadMedia(url: URL) async throws {
let data = try await URLSession.shared.data(from: url).0
let mediaURL = applicationSupportDirectory
.appendingPathComponent("Downloads")
.appendingPathComponent(url.lastPathComponent)
try data.write(to: mediaURL)
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try mediaURL.setResourceValues(resourceValues)
}
}
Debugging Storage Issues
Audit Backup Size
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")
if size > 100 * 1_000_000 {
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 {
()
isExcludedFromBackup(url: fileURL) {
()
}
}
}
}
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 |
try data.write(to: url, options: .completeFileProtection)
try FileManager.default.createDirectory(
at: url,
withIntermediateDirectories: true,
attributes: [.protectionKey: FileProtectionType.complete]
)
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)