关于 axiom-storage-diag
面向开发者的 iOS 文件存储诊断技能,用于排查文件丢失、无法访问或备份异常等问题。它从存储位置、文件保护设置和备份排除项入手,提供检查步骤与修正模式。
主要功能
- 01诊断存储位置问题
- 02检查文件保护设置
- 03排查备份体积异常
- 04临时文件与缓存处理指南
- 05检查备份排除设置
适用场景
- 01追查设备重启后文件消失的问题
- 02排查设备锁定时后台无法访问文件
- 03减少可重新下载内容占用的备份空间
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-storage-diag安装后在 Claude Code 中输入/axiom-storage-diag确认已加载。
详细介绍
本地文件存储诊断
概述
核心原则 90% 的文件存储问题源于选择了错误的存储位置、误解了文件保护级别,或遗漏了备份排除设置,而非 iOS 文件系统漏洞。
iOS 文件系统经过了数百万个应用和设备的实战验证。如果文件消失、无法访问或导致备份问题,几乎总是存储位置选择或保护配置有误。
危险信号 — 怀疑文件存储问题
如果出现以下任一情况:
- 设备重启后文件莫名消失
- 文件随机消失(创建数周后)
- 应用备份大小异常庞大(>500 MB)
- 应用进入后台再回到前台后出现“找不到文件”
- 设备锁定时无法访问文件
- 用户报告 iOS 更新后数据丢失
- 后台任务无法访问文件
❌ 禁止“iOS 删除了我的文件,文件系统坏了”
- iOS 文件系统每天为所有应用处理数十亿个文件
- 系统行为有文档说明且可预测
- 99% 的问题都是存储位置或保护设置不匹配
必须先执行的步骤
务必先检查这些项目(再更改代码):
// 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")
}
}
决策树
文件消失
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
文件无法访问
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
备份过大
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")
}
```
常见症状模式
模式 1:tmp/ 中的文件消失
症状:重启后,甚至在应用运行期间,临时文件也会丢失
原因:系统会积极清理 tmp/
修复:
// ❌ 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)
模式 2:缓存被清理,数据丢失
症状:下载的内容在数周后消失
原因:存储空间不足时会清理 Caches/(预期行为)
修复:按需重新下载,或者如果内容无法重新生成,则移至 Application Support
// ✅ 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)!
}
模式 3:.complete 保护阻止后台访问
症状:后台任务失败并显示“权限被拒绝”
原因:设备锁定时,无法访问采用 .complete 保护的文件
修复:
// ❌ 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
模式 4:下载内容导致备份膨胀
症状:应用备份超过 1 GB,应用被拒绝上架或用户投诉
原因:下载的内容存放在 Documents/ 中,或未标记为排除项
修复:
// ✅ 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)
}
生产环境危机情景
症状:用户报告 iOS 更新后照片丢失
诊断步骤:
-
检查存储位置(5 分钟):
// Were photos in Caches/? let photosInCaches = path.contains("/Caches/") // If yes → system purged them (expected) -
检查是否已备份(5 分钟):
// Check if excluded from backup let excluded = try? url.resourceValues( forKeys: [.isExcludedFromBackupKey] ).isExcludedFromBackup // If excluded=true AND not synced → lost -
检查迁移路径(10 分钟):
- 应用容器路径是否发生变化?
- 是否已从旧位置迁移数据?
根本原因(90% 的情况):
- 照片存放在 Caches/ 中(存储空间不足时会被清理)
- 照片被排除在备份之外,且没有云同步
- 主要 iOS 更新后缺少迁移代码
修复:
- 用户照片必须存放在 Documents/ 中
- 切勿将用户创建的内容排除在备份之外
- 用户内容务必进行云同步或备份
快速诊断清单
遇到任何存储问题时运行此清单:
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 ===")
}
相关技能
axiom-storage— 正确选择存储位置axiom-file-protection-ref— 了解保护级别axiom-storage-management-ref— 清理行为和容量 API
最后更新:2025-12-12 技能类型:诊断
评论
支持 Markdown