axiom-storage-management-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiOS Storage Management Reference
iOS 存储管理参考文档
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'?"
用途:存储压力、清理策略、磁盘空间及URL资源值的综合参考文档
适用版本:iOS 5.0+(基础功能),iOS 11.0+(现代容量API)
背景:解答「iOS是否提供将文件标记为‘最后才清理’的方法?」这一问题
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
当你需要以下操作时,可使用本技能:
- 了解iOS文件清理行为
- 正确检查可用磁盘空间
- 为缓存文件设置清理优先级
- 将文件排除在备份外
- 监控存储压力
- 将文件标记为可清理
- 了解容量相关API
- 处理「存储空间不足」场景
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 pressure
Library/Caches/ - ,
Documents/→ Never purgedApplication Support/
-
Capacity checking (explicit strategy):
- — For must-save data
volumeAvailableCapacityForImportantUsage - — For nice-to-have data
volumeAvailableCapacityForOpportunisticUsage - Check before saving, choose location based on available space
「iOS是否提供将文件标记为‘最后才清理’的方法?」
答案:没有直接的方法,但iOS提供两种实现思路:
-
基于存储位置的清理(隐式优先级):
- → 被主动清理(随时可能)
tmp/ - → 存储压力下会被清理
Library/Caches/ - 、
Documents/→ 永远不会被清理Application Support/
-
容量检查(显式策略):
- — 用于必须保存的数据
volumeAvailableCapacityForImportantUsage - — 用于非必需的数据
volumeAvailableCapacityForOpportunisticUsage - 保存前检查容量,根据可用空间选择存储位置
URL Resource Values for Storage
存储相关的URL资源值
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+ |
| 资源键 | 类型 | 用途 | 适用版本 |
|---|---|---|---|
| Int64 | 总可用空间 | iOS 5.0+ |
| Int64 | 必需文件可用空间 | iOS 11.0+ |
| Int64 | 可选文件可用空间 | iOS 11.0+ |
| Int64 | 总存储容量 | iOS 5.0+ |
| Bool | 排除在iCloud/iTunes备份外 | iOS 5.1+ |
| Bool | 系统可在存储压力下删除 | iOS 9.0+ |
| Int64 | 实际占用磁盘空间 | iOS 5.0+ |
| Int64 | 总占用空间(含元数据) | 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()
}swift
// ✅ 正确做法:在保存前检查合适的容量
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)")
return false
}
}
// 使用示例
if checkSpaceBeforeSaving(fileSize: imageData.count, isEssential: true) {
try imageData.write(to: documentsURL.appendingPathComponent("photo.jpg"))
} else {
showLowStorageAlert()
}Important vs Opportunistic Capacity
必需容量 vs 可选容量
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
}volumeAvailableCapacityForImportantUsage:
- 为关键操作预留的空间
- 适用场景:用户创建的内容、必须保存的数据
- 系统会更严格地保留此空间
- 阈值更高
volumeAvailableCapacityForOpportunisticUsage:
- 为可选操作提供的可用空间
- 适用场景:缓存、缩略图、预加载内容
- 阈值更低(系统可能已处于存储压力下)
- 表示「如果需要可以执行,但系统存储空间即将不足」
swift
// ✅ 正确做法:为不同类型数据设置不同阈值
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
isExcludedFromBackup
Files in are automatically excluded from backup, but you should explicitly mark re-downloadable files in other directories.
Caches/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
Caches/swift
// ✅ 正确做法:将可重新下载的大文件排除在备份外
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)
}何时排除备份:
- ✅ 可重新获取的下载内容
- ✅ 生成的缩略图
- ✅ 缓存的API响应
- ✅ 来自服务器的大媒体文件
- ❌ 用户创建的内容(始终需要备份)
- ❌ 无法重新生成的应用数据
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
}swift
// ✅ 检查文件是否已排除在备份外
func isExcludedFromBackup(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.isExcludedFromBackupKey])
return values?.isExcludedFromBackup ?? false
}Purgeable Files
可清理文件
isPurgeable
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 are already purgeable by location. Setting is advisory for files in other locations.
Caches/isPurgeable将文件标记为系统可自动清理的候选文件。
swift
// ✅ 正确做法:将缓存文件标记为可清理
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)
}注意:目录下的文件已通过存储位置默认标记为可清理。为其他目录下的文件设置仅作为系统的参考建议。
Caches/isPurgeableImplicit Purge Priority (Location-Based)
隐式清理优先级(基于存储位置)
iOS purges files based on location, not explicit priority flags.
iOS根据文件的存储位置而非显式优先级标记来清理文件。
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最先被清理(主动):
└── tmp/
- 清理时机:随时(即使应用在运行中)
- 生命周期:数小时至数天
- 适用场景:真正的临时中间文件
其次被清理(存储压力下):
└── Library/Caches/
- 清理时机:系统需要空间时
- 生命周期:数周至数月(若空间充足)
- 适用场景:可重新下载、可重新生成的内容
永不被清理(永久存储):
├── Documents/
│ - 是否备份:✅ 是
│ - 是否被清理:❌ 永不(除非应用被删除)
│ - 适用场景:用户创建的内容
│
└── Library/Application Support/
- 是否备份:✅ 是
- 是否被清理:❌ 永不(除非应用被删除)
- 适用场景:关键应用数据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
}swift
// ✅ 正确做法:根据清理优先级需求选择存储位置
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 // 永不清理
case cacheable // 存储压力下清理
case temporary // 主动清理
}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)
}
}
}swift
// ✅ 正确做法:监控存储空间并主动清理
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 { // 可用空间不足10%
print("⚠️ 检测到存储空间不足,开始清理...")
cleanupCaches()
}
}
func cleanupCaches() {
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
// 删除旧缓存文件
let fileManager = FileManager.default
guard let files = try? fileManager.contentsOfDirectory(
at: cacheURL,
includingPropertiesForKeys: [.contentModificationDateKey]
) else { return }
// 按修改日期排序
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)
}
// 先删除最旧的文件
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)
}swift
// ✅ 正确做法:注册后台任务以清理存储空间
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
获取准确文件大小
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")swift
// ✅ 正确做法:获取实际磁盘占用(含文件系统开销)
func getFileSize(url: URL) -> Int64? {
let values = try? url.resourceValues(forKeys: [
.fileAllocatedSizeKey,
.totalFileAllocatedSizeKey
])
// 使用totalFileAllocatedSize获取准确磁盘占用
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("缓存占用 \(cacheSize / 1_000_000) MB")Common Patterns
常见模式
Pattern 1: Smart Download Based on Available Space
模式1:基于可用空间的智能下载
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))
}swift
// ✅ 正确做法:仅当空间充足时下载可选内容
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("跳过下载 - 存储空间不足")
return
}
// 执行下载
let data = try await URLSession.shared.data(from: url).0
try data.write(to: cachesDirectory.appendingPathComponent(url.lastPathComponent))
}Pattern 2: Progressive Cache Cleanup
模式2:渐进式缓存清理
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)
}
}swift
// ✅ 正确做法:接近存储限制时清理缓存
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) // 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
// 可用空间不足200 MB时清理
return available < 200 * 1_000_000
}
func cleanupOldestFiles(targetSize: Int64) {
// 删除最旧的缓存文件直到容量达标
// (实现方式参考之前的示例)
}
}Pattern 3: Exclude Downloaded Media from Backup
模式3:将下载的媒体文件排除在备份外
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)
}
}swift
// ✅ 正确做法:下载的播客/视频管理
class MediaDownloader {
func downloadMedia(url: URL) async throws {
let data = try await URLSession.shared.data(from: url).0
// 存储在Application Support目录(而非Caches,以便持久保存)
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
审计备份大小
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?")
}
}
}
}swift
// ✅ 检查备份内容
func auditBackupSize() {
let documentsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let size = getDirectorySize(url: documentsURL)
print("Documents目录(已备份):\(size / 1_000_000) MB")
// 检查应排除备份的大文件
if size > 100 * 1_000_000 { // 超过100 MB
print("⚠️ 备份体积过大 - 检查可重新下载的文件")
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("大文件:\(fileURL.lastPathComponent) (\(size / 1_000_000) MB)")
// 检查是否已排除备份
if !isExcludedFromBackup(url: fileURL) {
print("⚠️ 是否应将此文件排除在备份外?")
}
}
}
}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 |
| 任务 | API | 代码示例 |
|---|---|---|
| 检查必需文件可用空间 | | |
| 检查缓存可用空间 | | |
| 排除备份 | | |
| 标记为可清理 | | |
| 获取文件大小 | | |
| 清理优先级 | 基于存储位置 | 使用 |
File Protection Quick Reference
文件保护快速参考
Set encryption level per file. See for full guide.
axiom-file-protection-ref| 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 |
swift
// 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)")为单个文件设置加密级别。完整指南请参考。
axiom-file-protection-ref| 级别 | 可访问时机 | 适用场景 |
|---|---|---|
| 仅设备解锁时 | 密码、令牌、健康数据 |
| 首次解锁后若已打开则可访问 | 正在进行的下载、媒体录制 |
| 首次解锁后(默认) | 大多数应用数据 |
| 始终可访问,甚至解锁前 | 后台获取数据、推送负载 |
swift
// 为文件设置保护级别
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("保护级别:\(values.fileProtection ?? .none)")Related Skills
相关技能
- — Decide where to store files
axiom-storage - — File encryption and security
axiom-file-protection-ref - — Debug storage-related issues
axiom-storage-diag
Last Updated: 2025-12-12
Skill Type: Reference
Minimum iOS: 5.0 (basic), 11.0 (modern capacity APIs)
- — 选择文件存储位置
axiom-storage - — 文件加密与安全
axiom-file-protection-ref - — 调试存储相关问题
axiom-storage-diag
最后更新:2025-12-12
技能类型:参考文档
最低iOS版本:5.0(基础功能),11.0(现代容量API)