axiom-storage-diag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLocal File Storage Diagnostics
本地文件存储诊断
Overview
概述
Core principle 90% of file storage problems stem from choosing the wrong storage location, misunderstanding file protection levels, or missing backup exclusions—not iOS file system bugs.
The iOS file system is battle-tested across millions of apps and devices. If your files are disappearing, becoming inaccessible, or causing backup issues, the problem is almost always in storage location choice or protection configuration.
核心原则 90%的文件存储问题源于选择了错误的存储位置、对文件保护级别理解有误,或者遗漏了备份排除规则——而非iOS文件系统漏洞。
iOS文件系统经过数百万款应用和设备的实战检验。如果你的文件消失、无法访问或出现备份问题,几乎都是存储位置选择或保护配置方面的问题。
Red Flags — Suspect File Storage Issue
预警信号——疑似文件存储问题
If you see ANY of these:
- Files mysteriously disappear after device restart
- Files disappear randomly (weeks after creation)
- App backup size unexpectedly large (>500 MB)
- "File not found" after app background/foreground cycle
- Files inaccessible when device is locked
- Users report lost data after iOS update
- Background tasks can't access files
❌ FORBIDDEN "iOS deleted my files, the file system is broken"
- iOS file system handles billions of files daily across all apps
- System behavior is documented and predictable
- 99% of issues are location/protection mismatches
如果出现以下任意一种情况:
- 设备重启后文件神秘消失
- 文件随机消失(创建数周后)
- App备份大小异常过大(>500 MB)
- App前后台切换后出现「文件未找到」
- 设备锁定时文件无法访问
- 用户反馈iOS更新后数据丢失
- 后台任务无法访问文件
❌ 严禁 声称「iOS删除了我的文件,文件系统坏了」
- iOS文件系统每天在所有应用中处理数十亿个文件
- 系统行为有文档记录且可预测
- 99%的问题都是位置/保护配置不匹配导致的
Mandatory First Steps
强制第一步检查
ALWAYS check these FIRST (before changing code):
swift
// 1. Check WHERE file is stored
func diagnoseFileLocation(_ url: URL) {
let path = url.path
if path.contains("/tmp/") {
print("⚠️ File in tmp/ - system purges aggressively")
} else if path.contains("/Caches/") {
print("⚠️ File in Caches/ - purged under storage pressure")
} else if path.contains("/Documents/") {
print("✅ File in Documents/ - never purged, backed up")
} else if path.contains("/Library/Application Support/") {
print("✅ File in Application Support/ - never purged, backed up")
}
}
// 2. Check file protection level
func diagnoseFileProtection(_ url: URL) throws {
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
if let protection = attrs[.protectionKey] as? FileProtectionType {
print("Protection: \(protection)")
if protection == .complete {
print("⚠️ File inaccessible when device locked")
}
}
}
// 3. Check backup status
func diagnoseBackupStatus(_ url: URL) throws {
let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
if let excluded = values.isExcludedFromBackup {
print("Excluded from backup: \(excluded)")
}
}
// 4. Check file existence and size
func diagnoseFileState(_ url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
if let size = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int64 {
print("File exists, size: \(size) bytes")
}
} else {
print("❌ File does not exist")
}
}务必首先检查以下内容(修改代码之前):
swift
// 1. Check WHERE file is stored
func diagnoseFileLocation(_ url: URL) {
let path = url.path
if path.contains("/tmp/") {
print("⚠️ File in tmp/ - system purges aggressively")
} else if path.contains("/Caches/") {
print("⚠️ File in Caches/ - purged under storage pressure")
} else if path.contains("/Documents/") {
print("✅ File in Documents/ - never purged, backed up")
} else if path.contains("/Library/Application Support/") {
print("✅ File in Application Support/ - never purged, backed up")
}
}
// 2. Check file protection level
func diagnoseFileProtection(_ url: URL) throws {
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
if let protection = attrs[.protectionKey] as? FileProtectionType {
print("Protection: \(protection)")
if protection == .complete {
print("⚠️ File inaccessible when device locked")
}
}
}
// 3. Check backup status
func diagnoseBackupStatus(_ url: URL) throws {
let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
if let excluded = values.isExcludedFromBackup {
print("Excluded from backup: \(excluded)")
}
}
// 4. Check file existence and size
func diagnoseFileState(_ url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
if let size = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int64 {
print("File exists, size: \(size) bytes")
}
} else {
print("❌ File does not exist")
}
}Decision Tree
决策树
Files Disappeared
文件消失
Files missing? → Check where stored
├─ Disappeared after device restart
│ ├─ Was in tmp/? → EXPECTED (tmp/ purged on reboot)
│ │ → FIX: Move to Caches/ or Application Support/
│ │
│ ├─ Was in Caches/? → System purged (storage pressure)
│ │ → FIX: Move to Application Support/ if can't be regenerated
│ │
│ └─ Protection level .complete? → Inaccessible until unlock
│ → FIX: Wait for unlock or use .completeUntilFirstUserAuthentication
│
├─ Disappeared randomly (weeks later)
│ ├─ In Caches/? → System purged under storage pressure
│ │ → EXPECTED if re-downloadable
│ │ → FIX: Re-download when needed, or move to Application Support/
│ │
│ └─ In Documents or Application Support/?
│ → Check if user deleted app (purges all data)
│ → Check iOS update (rare, but check migration path)
│
└─ Only some files missing
→ Check isExcludedFromBackup + iCloud sync
→ Check if file names have special characters
→ Check file permissions文件丢失?→ 检查存储位置
├─ 设备重启后消失
│ ├─ 是否在tmp/目录?→ 符合预期(tmp/会在重启时被清理)
│ │ → 修复方案:移至Caches/或Application Support/
│ │
│ ├─ 是否在Caches/目录?→ 系统因存储压力清理了文件
│ │ → 修复方案:如果文件无法重新生成,移至Application Support/
│ │
│ └─ 保护级别为.complete?→ 解锁前无法访问
│ → 修复方案:等待解锁或使用.completeUntilFirstUserAuthentication
│
├─ 随机消失(数周后)
│ ├─ 在Caches/目录?→ 系统因存储压力清理了文件
│ │ → 如果可重新下载则符合预期
│ │ → 修复方案:需要时重新下载,或移至Application Support/
│ │
│ └─ 在Documents或Application Support/目录?
│ → 检查用户是否删除了应用(会清除所有数据)
│ → 检查iOS更新(罕见情况,但需检查迁移路径)
│
└─ 仅部分文件丢失
→ 检查isExcludedFromBackup设置 + iCloud同步
→ 检查文件名是否包含特殊字符
→ 检查文件权限Files Inaccessible
文件无法访问
Can't access file?
├─ Error: "No permission" or NSFileReadNoPermissionError
│ ├─ Device locked? → Check file protection
│ │ └─ .complete protection? → Wait for unlock
│ │ → FIX: Use .completeUntilFirstUserAuthentication
│ │
│ └─ Background task accessing? → .complete blocks background
│ → FIX: Change to .completeUntilFirstUserAuthentication
│
├─ File exists but read returns empty/nil
│ └─ Check actual file size on disk
│ → May be zero-byte file from failed write
│
└─ File exists in debugger but not at runtime
→ Check if using wrong directory (Documents vs Caches)
→ Check URL construction无法访问文件?
├─ 错误:"No permission"或NSFileReadNoPermissionError
│ ├─ 设备是否锁定?→ 检查文件保护级别
│ │ └─ 保护级别为.complete?→ 等待解锁
│ │ → 修复方案:使用.completeUntilFirstUserAuthentication
│ │
│ └─ 是否是后台任务访问?→ .complete会阻止后台访问
│ → 修复方案:改为.completeUntilFirstUserAuthentication
│
├─ 文件存在但读取返回空/nil
│ └─ 检查磁盘上的实际文件大小
│ → 可能是写入失败导致的零字节文件
│
└─ 调试器中存在文件但运行时不存在
→ 检查是否使用了错误的目录(Documents vs Caches)
→ 检查URL构造Backup Too Large
备份过大
App backup > 500 MB?
├─ Check Documents directory size
│ └─ Large files (>10 MB each)?
│ ├─ Can they be re-downloaded? → Move to Caches + isExcludedFromBackup
│ └─ User-created? → Keep in Documents (warn user if >1 GB)
│
├─ Check Application Support size
│ └─ Downloaded media/podcasts?
│ → Mark isExcludedFromBackup = true
│
└─ Audit backup with code:
```swift
func auditBackupSize() {
let docsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let size = getDirectorySize(url: docsURL)
print("Documents (backed up): \(size / 1_000_000) MB")
}
```App备份超过500 MB?
├─ 检查Documents目录大小
│ └─ 是否有大文件(单个>10 MB)?
│ ├─ 是否可重新下载?→ 移至Caches/并设置isExcludedFromBackup
│ └─ 是否是用户创建的?→ 保留在Documents中(如果超过1 GB需提醒用户)
│
├─ 检查Application Support目录大小
│ └─ 是否有下载的媒体/播客?
│ → 设置isExcludedFromBackup = true
│
└─ 用代码审计备份大小:
```swift
func auditBackupSize() {
let docsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let size = getDirectorySize(url: docsURL)
print("Documents (backed up): \(size / 1_000_000) MB")
}
```Common Patterns by Symptom
常见症状对应模式
Pattern 1: Files in tmp/ Disappear
模式1:tmp/目录下的文件消失
Symptom: Temp files missing after restart or even during app lifecycle
Cause: tmp/ is purged aggressively by system
Fix:
swift
// ❌ WRONG: Using tmp/ for anything that should persist
let tmpURL = FileManager.default.temporaryDirectory
let fileURL = tmpURL.appendingPathComponent("data.json")
try data.write(to: fileURL) // WILL BE DELETED
// ✅ CORRECT: Use Caches/ for re-generable data
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let fileURL = cacheURL.appendingPathComponent("data.json")
try data.write(to: fileURL)症状:临时文件在重启后甚至应用生命周期内消失
原因:系统会主动清理tmp/目录
修复方案:
swift
// ❌ WRONG: Using tmp/ for anything that should persist
let tmpURL = FileManager.default.temporaryDirectory
let fileURL = tmpURL.appendingPathComponent("data.json")
try data.write(to: fileURL) // WILL BE DELETED
// ✅ CORRECT: Use Caches/ for re-generable data
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let fileURL = cacheURL.appendingPathComponent("data.json")
try data.write(to: fileURL)Pattern 2: Caches Purged, Data Lost
模式2:Caches目录被清理,数据丢失
Symptom: Downloaded content disappears weeks later
Cause: Caches/ is purged under storage pressure (expected behavior)
Fix: Either re-download on demand OR move to Application Support if can't be regenerated
swift
// ✅ CORRECT: Handle missing cache gracefully
func loadCachedImage(url: URL) async throws -> UIImage {
let cacheURL = getCacheURL(for: url)
// Try cache first
if FileManager.default.fileExists(atPath: cacheURL.path),
let data = try? Data(contentsOf: cacheURL),
let image = UIImage(data: data) {
return image
}
// Cache miss - re-download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: cacheURL)
return UIImage(data: data)!
}症状:下载的内容数周后消失
原因:Caches/目录会在存储压力下被清理(符合预期行为)
修复方案:要么按需重新下载,要么如果文件无法重新生成则移至Application Support/
swift
// ✅ CORRECT: Handle missing cache gracefully
func loadCachedImage(url: URL) async throws -> UIImage {
let cacheURL = getCacheURL(for: url)
// Try cache first
if FileManager.default.fileExists(atPath: cacheURL.path),
let data = try? Data(contentsOf: cacheURL),
let image = UIImage(data: data) {
return image
}
// Cache miss - re-download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: cacheURL)
return UIImage(data: data)!
}Pattern 3: .complete Protection Blocks Background
模式3:.complete保护级别阻止后台访问
Symptom: Background tasks fail with "permission denied"
Cause: Files with .complete protection inaccessible when locked
Fix:
swift
// ❌ WRONG: .complete protection for background-accessed files
try data.write(to: url, options: .completeFileProtection)
// Background task fails when device locked
// ✅ CORRECT: Use .completeUntilFirstUserAuthentication
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
// Accessible in background after first unlock症状:后台任务因「权限不足」失败
原因:设置.complete保护级别的文件在设备锁定时无法访问
修复方案:
swift
// ❌ WRONG: .complete protection for background-accessed files
try data.write(to: url, options: .completeFileProtection)
// Background task fails when device locked
// ✅ CORRECT: Use .completeUntilFirstUserAuthentication
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
// Accessible in background after first unlockPattern 4: Backup Bloat from Downloaded Content
模式4:下载内容导致备份臃肿
Symptom: App backup >1 GB, app rejected or users complain
Cause: Downloaded content in Documents/ or not marked excluded
Fix:
swift
// ✅ CORRECT: Exclude re-downloadable content
func downloadPodcast(url: URL) async throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
// Download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: podcastURL)
// Mark excluded from backup
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try podcastURL.setResourceValues(resourceValues)
}症状:App备份超过1 GB,应用被拒或用户投诉
原因:下载的内容存放在Documents/目录中,或未标记为排除备份
修复方案:
swift
// ✅ CORRECT: Exclude re-downloadable content
func downloadPodcast(url: URL) async throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
// Download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: podcastURL)
// Mark excluded from backup
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try podcastURL.setResourceValues(resourceValues)
}Production Crisis Scenario
生产环境紧急场景
SYMPTOM: Users report lost photos after iOS update
DIAGNOSIS STEPS:
-
Check storage location (5 min):swift
// Were photos in Caches/? let photosInCaches = path.contains("/Caches/") // If yes → system purged them (expected) -
Check if backed up (5 min):swift
// Check if excluded from backup let excluded = try? url.resourceValues( forKeys: [.isExcludedFromBackupKey] ).isExcludedFromBackup // If excluded=true AND not synced → lost -
Check migration path (10 min):
- Did app container path change?
- Did we migrate data from old location?
ROOT CAUSES (90% of cases):
- Photos in Caches/ (purged under storage pressure)
- Photos excluded from backup + no cloud sync
- Migration code missing after major iOS update
FIX:
- User photos MUST be in Documents/
- Never exclude user-created content from backup
- Always have cloud sync OR backup for user content
症状:用户反馈iOS更新后照片丢失
诊断步骤:
-
检查存储位置(5分钟):swift
// Were photos in Caches/? let photosInCaches = path.contains("/Caches/") // If yes → system purged them (expected) -
检查是否已备份(5分钟):swift
// Check if excluded from backup let excluded = try? url.resourceValues( forKeys: [.isExcludedFromBackupKey] ).isExcludedFromBackup // If excluded=true AND not synced → lost -
检查迁移路径(10分钟):
- App容器路径是否变更?
- 是否从旧位置迁移了数据?
根本原因(90%的情况):
- 照片存放在Caches/目录(被系统清理,符合预期)
- 照片被排除备份且未同步到云端
- 重大iOS更新后缺少数据迁移代码
修复方案:
- 用户照片必须存放在Documents/目录
- 绝不要将用户创建的内容排除在备份之外
- 用户内容必须有云同步或备份机制
Quick Diagnostic Checklist
快速诊断检查清单
Run this on any storage problem:
swift
func diagnoseStorageIssue(fileURL: URL) {
print("=== Storage Diagnosis ===")
// 1. Location
diagnoseFileLocation(fileURL)
// 2. Protection
try? diagnoseFileProtection(fileURL)
// 3. Backup status
try? diagnoseBackupStatus(fileURL)
// 4. File state
diagnoseFileState(fileURL)
// 5. Directory size
if let parentURL = fileURL.deletingLastPathComponent() as URL? {
let size = getDirectorySize(url: parentURL)
print("Parent directory size: \(size / 1_000_000) MB")
}
print("=== End Diagnosis ===")
}遇到任何存储问题时运行以下代码:
swift
func diagnoseStorageIssue(fileURL: URL) {
print("=== Storage Diagnosis ===")
// 1. Location
diagnoseFileLocation(fileURL)
// 2. Protection
try? diagnoseFileProtection(fileURL)
// 3. Backup status
try? diagnoseBackupStatus(fileURL)
// 4. File state
diagnoseFileState(fileURL)
// 5. Directory size
if let parentURL = fileURL.deletingLastPathComponent() as URL? {
let size = getDirectorySize(url: parentURL)
print("Parent directory size: \(size / 1_000_000) MB")
}
print("=== End Diagnosis ===")
}Related Skills
相关技能
- — Correct storage location decisions
axiom-storage - — Understanding protection levels
axiom-file-protection-ref - — Purge behavior and capacity APIs
axiom-storage-management-ref
Last Updated: 2025-12-12
Skill Type: Diagnostic
- — 正确的存储位置决策
axiom-storage - — 理解保护级别
axiom-file-protection-ref - — 清理行为和容量API
axiom-storage-management-ref
最后更新时间:2025-12-12
技能类型:诊断工具