iOS 文件系统:沙盒、备份规则与数据持久化全解析
面试高频:iOS 沙盒四大目录(App/Documents/Library/tmp)各自会不会备份、会不会被清、UserDefaults 与 Keychain 怎么选、File Protection 加密级别,一篇讲透原生存储。
一句话概括
iOS 给每个 App 划了一个沙盒(Sandbox):你只能读写自己目录里的东西,碰不到别家 App,也碰不到系统目录。沙盒内按用途分成 AppName.app(只读包)、Documents(用户数据,会备份)、Library(Caches/不备份可清、Preferences/Application Support 会备份)、tmp(临时,随时清)。选错目录,轻则 iCloud 被缓存撑爆、应用审核被拒,重则用户数据丢失。
对跨端同学,这套目录对应 path_provider 的 getApplicationDocumentsDirectory() 等 API 的底层。面试最爱问:“下载的文件该放哪”“UserDefaults 能不能存 token”“Keychain 卸载后还在吗”“Documents 会被系统清吗”。核心结论先给出:敏感信息(token/密码)存 Keychain,绝不放 UserDefaults;可重新下载的缓存放 Caches,别塞 Documents。
核心知识点
1. 沙盒目录地图
1
2
3
4
5
6
7
8
// 沙盒根:/var/mobile/Containers/Data/Application/{UUID}/
// 注意 UUID 每次重装都变,永远用 API 取路径,别硬编码
Bundle.main.bundlePath // AppName.app/ 只读包
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) // Documents/
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) // Library/Caches/
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) // Library/Application Support/
FileManager.default.temporaryDirectory // tmp/
| 目录 | 内容 | 备份到 iCloud | 系统会清理 |
|---|---|---|---|
AppName.app/ | 二进制 + 资源(只读) | 否 | 否(只读) |
Documents/ | 用户生成的文档、离线内容 | ✅ 是 | 否 |
Library/Preferences/ | UserDefaults 数据 | ✅ 是 | 否 |
Library/Application Support/ | 配置、Core Data 存储 | ✅ 是 | 否 |
Library/Caches/ | 网络缓存、缩略图 | 否 | ✅ 空间不足时可能 |
tmp/ | 下载中间文件、临时数据 | 否 | ✅ 随时 |
选目录口诀:
- 用户创建的、丢了心疼的 →
Documents/ - 能重新下载/重新算的 →
Library/Caches/ - 应用运行必需但用户不该直接看的配置 →
Library/Application Support/ - 用完即弃的 →
tmp/(App 退出时也该自己清)
2. NSFileManager:现代 URL 优先
iOS 推荐用 URL 而不是字符串路径操作文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let fm = FileManager.default
let doc = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
// 写
let file = doc.appendingPathComponent("notes.txt")
try "hello".write(to: file, atomically: true, encoding: .utf8)
// 读
let text = try String(contentsOf: file, encoding: .utf8)
let data = try Data(contentsOf: file)
// 存在性 & 属性
if fm.fileExists(atPath: file.path) {
let attr = try fm.attributesOfItem(atPath: file.path)
let size = attr[.size] as? Int64
}
// 建目录 / 列目录 / 删
try fm.createDirectory(at: doc.appendingPathComponent("img"),
withIntermediateDirectories: true)
let items = try fm.contentsOfDirectory(at: doc, includingPropertiesForKeys: nil)
try fm.removeItem(at: file)
注意:
Application Support目录可能不存在,写之前先createDirectory(withIntermediateDirectories: true)。
3. 不想备份?标记 isExcludedFromBackup
如果一个文件必须放 Documents/Application Support 但不想占用户 iCloud 空间(如大体积离线库),可以标记:
1
2
3
4
var rv = URLResourceValues()
rv.isExcludedFromBackup = true
var url = doc.appendingPathComponent("offline.db")
try url.setResourceValues(rv)
警告:Apple 明确说只在”确实必要”时用。别把整个 Documents 标成不备份来逃避规则——可重新生成的缓存本就该放 Caches。审核时滥用会被质疑。
4. UserDefaults vs Keychain:token 存哪?
1
2
3
4
5
6
// UserDefaults:轻量键值,明文存在 Library/Preferences/
UserDefaults.standard.set("theme_dark", forKey: "theme")
let theme = UserDefaults.standard.string(forKey: "theme")
// Keychain:系统级加密,适合敏感数据
// 写入(见下)
| 场景 | 存哪 |
|---|---|
| 主题、字体大小等偏好 | UserDefaults |
| 启动次数、版本号 | UserDefaults |
| 访问令牌 / 密码 / 支付信息 | Keychain |
| 生物识别保护的数据 | Keychain |
| 大体积配置(>100KB) | Application Support 里的 JSON |
UserDefaults 的坑:数据是明文的、不适合敏感信息;只支持 Property List 类型(Data/String/Number/Date/Array/Dictionary),塞自定义对象会丢;key 拼错只返回 nil 不报错。
Keychain 关键事实(面试必答):数据写入即加密;同一 Team 下的 App 可通过 access group 共享;kSecAttrSynchronizable 可选择性同步到 iCloud。App 卸载后 Keychain 是否保留,看 iOS 版本:iOS 10.3 之前卸载后保留,iOS 10.3 及之后默认随卸载清除(除非用了 access group 跨 App 共享)。所以”卸载重装登录态没了”在现代系统上是正常行为,别当 bug。
最小可用写入示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
import Security
func saveToKeychain(_ data: Data, account: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.yourapp",
kSecAttrAccount as String: account,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary) // 先删旧的
return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}
跨端对照:
flutter_secure_storage、react-native-keychain底层就是 Keychain。调试发现 token 丢失,先查”是否卸载重装过”和”Team ID 是否变过”。
5. File Protection:文件级加密
iOS 提供文件级加密,按敏感度选级别(FileProtectionType):
.complete:仅设备解锁时可访问(最严).completeUnlessOpen:解锁后打开,之后锁屏句柄仍可用(适合后台写入).completeUntilFirstUserAuthentication:系统默认,首次解锁后任何状态可访问.none:不加密
1
2
3
fm.createFile(atPath: url.path, contents: data, attributes: [
.protectionKey: FileProtectionType.complete
])
影响:后台任务 / 通知扩展 / Widget 需要访问文件时,必须选 completeUntilFirstUserAuthentication(默认级)或更宽松的,否则设备锁定时读不到。
其实你每天都在用
path_provider的getApplicationDocumentsDirectory():底层就是Documents/,会进 iCloud 备份- 图片缓存框架(SDWebImage 等)存缩略图:放
Library/Caches/,清空间时可能被系统清 - 登录 token:存 Keychain,不是 UserDefaults(明文=裸奔)
- App”清理缓存”按钮:清的是
Caches+tmp - Core Data / SQLite 数据库文件:放
Application Support/或Documents/(用户数据) - 下载大文件先写
tmp/,完成再move到Documents/:避免下载中途占用备份空间 - iCloud 备份爆满被用户吐槽:多半是把可重下的缓存误放
Documents且没标不备份 - 启动次数 / 是否首次启动:
UserDefaults.standard.integer(forKey: "launchCount")
常见误解(FAQ)
❌ 误区一:”下载的文件放 Documents 最稳,反正不会丢”
Documents 会进 iCloud 备份。把可重新下载的缓存、临时媒体塞进 Documents,后果是:用户 iCloud 空间被你撑爆、审核可能被拒(指南要求只备份用户数据)。正确做法是可重下的放 Caches/,真正的用户文档才放 Documents/。
❌ 误区二:”UserDefaults 也能存登录 token,省事”
UserDefaults 是明文存在 Library/Preferences/,越狱或拿到备份就能直接读,等于把密码写在便利贴上。任何令牌、密码、支付信息都必须进 Keychain。UserDefaults 只配存主题、开关、计数这类非敏感偏好。
❌ 误区三:”App 卸载了 Keychain 还在,所以 token 永远不用重新登录”
这是旧认知。iOS 10.3 之前 Keychain 卸载后保留,但 iOS 10.3 起默认随 App 卸载一起清除(仅 access group 跨 App 共享的项例外)。现代系统上卸载重装后登录态丢失是正常现象,服务端要有”token 失效重新登录”的兜底,别假设 Keychain 永生。
❌ 误区四:”Documents 里的文件系统会定时清理,像 Caches 一样”
不会。Documents/ 在正常情况下不会被系统自动清理,只有用户手动”删除 App 数据”或卸载才会没。反倒是 Caches/ 在空间不足时可能被清、tmp/ 随时可能清。所以”丢了心疼且不能重下”的才放 Documents;”丢了能重来”的放 Caches/tmp。
❌ 误区五:”沙盒意味着 App 完全读不到外部任何文件”
沙盒限制的是写和跨 App 访问,但 App 可以通过系统提供的授权机制读外部文件:比如 UIDocumentPickerViewController、相册授权、Photo Kit、文件 App 的 Security-Scoped Bookmark 等。只是不能像桌面系统那样随意 open("/Users/xxx/...")。
一句话总结
iOS 存储的底层逻辑就一句:“沙盒隔离 + 目录分工 + 敏感进 Keychain”——Documents 存用户数据会备份、Caches/tmp 不备份可被清、UserDefaults 只放非敏感偏好、token 和密码必须进 Keychain、大文件用 File Protection 加锁。把”这份数据丢了心疼不心疼、能不能重来、敏不敏感”三个问题答完,该放哪个目录自然就清楚了,跨端项目里也就不会再出现”iCloud 爆满”“token 明文泄漏”“卸载后还以为登录着”这类低级事故。