axiom-storage-diag by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-storage-diag核心原则 90% 的文件存储问题源于选择了错误的存储位置、误解了文件保护级别或缺少备份排除项——而非 iOS 文件系统的错误。
iOS 文件系统经过数百万应用和设备的实战检验。如果你的文件消失、变得无法访问或导致备份问题,问题几乎总是出在存储位置的选择或保护配置上。
如果你看到以下任何一种情况:
❌ 禁止 “iOS 删除了我的文件,文件系统坏了”
务必先检查这些(在修改代码之前):
// 1. 检查文件存储位置
func diagnoseFileLocation(_ url: URL) {
let path = url.path
if path.contains("/tmp/") {
print("⚠️ 文件位于 tmp/ - 系统会积极清理")
} else if path.contains("/Caches/") {
print("⚠️ 文件位于 Caches/ - 存储压力下会被清理")
} else if path.contains("/Documents/") {
print("✅ 文件位于 Documents/ - 永不清理,会备份")
} else if path.contains("/Library/Application Support/") {
print("✅ 文件位于 Application Support/ - 永不清理,会备份")
}
}
// 2. 检查文件保护级别
func diagnoseFileProtection(_ url: URL) throws {
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
if let protection = attrs[.protectionKey] as? FileProtectionType {
print("保护级别: \(protection)")
if protection == .complete {
print("⚠️ 设备锁定时文件无法访问")
}
}
}
// 3. 检查备份状态
func diagnoseBackupStatus(_ url: URL) throws {
let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
if let excluded = values.isExcludedFromBackup {
print("已从备份中排除: \(excluded)")
}
}
// 4. 检查文件存在性和大小
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("文件存在,大小: \(size) 字节")
}
} else {
print("❌ 文件不存在")
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
文件缺失? → 检查存储位置
├─ 设备重启后消失
│ ├─ 在 tmp/ 中? → 预期行为(tmp/ 在重启时清理)
│ │ → 修复:移动到 Caches/ 或 Application Support/
│ │
│ ├─ 在 Caches/ 中? → 系统清理(存储压力)
│ │ → 修复:如果无法重新生成,则移动到 Application Support/
│ │
│ └─ 保护级别为 .complete? → 解锁前无法访问
│ → 修复:等待解锁或使用 .completeUntilFirstUserAuthentication
│
├─ 随机消失(数周后)
│ ├─ 在 Caches/ 中? → 系统在存储压力下清理
│ │ → 如果可重新下载,则为预期行为
│ │ → 修复:需要时重新下载,或移动到 Application Support/
│ │
│ └─ 在 Documents 或 Application Support/ 中?
│ → 检查用户是否删除了应用(清除所有数据)
│ → 检查 iOS 更新(罕见,但检查迁移路径)
│
└─ 仅部分文件缺失
→ 检查 isExcludedFromBackup + iCloud 同步
→ 检查文件名是否包含特殊字符
→ 检查文件权限
无法访问文件?
├─ 错误:"No permission" 或 NSFileReadNoPermissionError
│ ├─ 设备已锁定? → 检查文件保护
│ │ └─ .complete 保护? → 等待解锁
│ │ → 修复:使用 .completeUntilFirstUserAuthentication
│ │
│ └─ 后台任务正在访问? → .complete 会阻止后台访问
│ → 修复:更改为 .completeUntilFirstUserAuthentication
│
├─ 文件存在但读取返回空值/nil
│ └─ 检查磁盘上的实际文件大小
│ → 可能是写入失败产生的零字节文件
│
└─ 调试器中文件存在但运行时不存在
→ 检查是否使用了错误的目录(Documents 与 Caches)
→ 检查 URL 构造
应用备份 > 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(已备份): \(size / 1_000_000) MB")
}
```
症状:重启后甚至应用生命周期内临时文件丢失
原因:tmp/ 会被系统积极清理
修复:
// ❌ 错误:对任何应持久化的内容使用 tmp/
let tmpURL = FileManager.default.temporaryDirectory
let fileURL = tmpURL.appendingPathComponent("data.json")
try data.write(to: fileURL) // 将被删除
// ✅ 正确:对可重新生成的数据使用 Caches/
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let fileURL = cacheURL.appendingPathComponent("data.json")
try data.write(to: fileURL)
症状:下载的内容数周后消失
原因:Caches/ 在存储压力下会被清理(预期行为)
修复:要么按需重新下载,要么如果无法重新生成则移动到 Application Support
// ✅ 正确:优雅处理缓存缺失
func loadCachedImage(url: URL) async throws -> UIImage {
let cacheURL = getCacheURL(for: url)
// 先尝试缓存
if FileManager.default.fileExists(atPath: cacheURL.path),
let data = try? Data(contentsOf: cacheURL),
let image = UIImage(data: data) {
return image
}
// 缓存未命中 - 重新下载
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: cacheURL)
return UIImage(data: data)!
}
症状:后台任务因“权限被拒绝”而失败
原因:具有 .complete 保护的文件在锁定时无法访问
修复:
// ❌ 错误:对后台访问的文件使用 .complete 保护
try data.write(to: url, options: .completeFileProtection)
// 设备锁定时后台任务失败
// ✅ 正确:使用 .completeUntilFirstUserAuthentication
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
// 首次解锁后可在后台访问
症状:应用备份 >1 GB,应用被拒绝或用户投诉
原因:下载的内容位于 Documents/ 中或未标记为排除
修复:
// ✅ 正确:排除可重新下载的内容
func downloadPodcast(url: URL) async throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
// 下载
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: podcastURL)
// 标记为从备份中排除
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try podcastURL.setResourceValues(resourceValues)
}
症状:用户报告 iOS 更新后照片丢失
诊断步骤:
检查存储位置(5 分钟):
// 照片在 Caches/ 中吗?
let photosInCaches = path.contains("/Caches/")
// 如果是 → 系统清理了它们(预期行为)
检查是否已备份(5 分钟):
// 检查是否已从备份中排除
let excluded = try? url.resourceValues(
forKeys: [.isExcludedFromBackupKey]
).isExcludedFromBackup
// 如果 excluded=true 且未同步 → 丢失
检查迁移路径(10 分钟):
根本原因(90% 的情况):
修复:
在任何存储问题上运行此检查:
func diagnoseStorageIssue(fileURL: URL) {
print("=== 存储诊断 ===")
// 1. 位置
diagnoseFileLocation(fileURL)
// 2. 保护
try? diagnoseFileProtection(fileURL)
// 3. 备份状态
try? diagnoseBackupStatus(fileURL)
// 4. 文件状态
diagnoseFileState(fileURL)
// 5. 目录大小
if let parentURL = fileURL.deletingLastPathComponent() as URL? {
let size = getDirectorySize(url: parentURL)
print("父目录大小: \(size / 1_000_000) MB")
}
print("=== 诊断结束 ===")
}
axiom-storage — 正确的存储位置决策axiom-file-protection-ref — 理解保护级别axiom-storage-management-ref — 清理行为和容量 API最后更新 : 2025-12-12 技能类型 : 诊断
每周安装数
96
仓库
GitHub 星标数
606
首次出现
Jan 21, 2026
安全审计
已安装于
opencode81
codex75
claude-code75
gemini-cli74
cursor72
github-copilot70
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.
If you see ANY of these:
❌ FORBIDDEN "iOS deleted my files, the file system is broken"
ALWAYS check these FIRST (before changing code):
// 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")
}
```
Symptom : Temp files missing after restart or even during app lifecycle
Cause : tmp/ is purged aggressively by system
Fix :
// ❌ 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)
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
// ✅ 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)!
}
Symptom : Background tasks fail with "permission denied"
Cause : Files with .complete protection inaccessible when locked
Fix :
// ❌ 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
Symptom : App backup >1 GB, app rejected or users complain
Cause : Downloaded content in Documents/ or not marked excluded
Fix :
// ✅ 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)
}
SYMPTOM : Users report lost photos after iOS update
DIAGNOSIS STEPS :
Check storage location (5 min):
// Were photos in Caches/?
let photosInCaches = path.contains("/Caches/")
// If yes → system purged them (expected)
Check if backed up (5 min):
// 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):
ROOT CAUSES (90% of cases):
FIX :
Run this on any storage problem:
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 — Correct storage location decisionsaxiom-file-protection-ref — Understanding protection levelsaxiom-storage-management-ref — Purge behavior and capacity APIsLast Updated : 2025-12-12 Skill Type : Diagnostic
Weekly Installs
96
Repository
GitHub Stars
606
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode81
codex75
claude-code75
gemini-cli74
cursor72
github-copilot70
Clerk Swift iOS 原生集成指南:SwiftUI/UIKit 认证实现与快速开始
904 周安装