本篇目标
理解 iOS 沙盒目录结构,掌握文件路径获取、文本与 Data 读写、JSON 文件的编码与解码,能独立完成文件的保存、读取、删除与目录管理。
一、iOS 沙盒机制
iOS 应用运行在沙盒中,每个应用只能访问自己的目录,无法访问其他应用的数据。
沙盒的好处:
数据隔离,安全性高
卸载应用时自动清理数据
不同应用之间互不干扰
沙盒目录结构:
App Sandbox/
├── Documents/ // 用户数据,iTunes 备份
├── Library/
│ ├── Caches/ // 缓存,可能被系统清理
│ ├── Preferences/ // UserDefaults 存储
│ └── Application Support/ // 应用支持文件
├── tmp/ // 临时文件,随时可能被清理
└── SystemData/ // 系统数据各目录用途:
目录 | 用途 | 是否备份 |
|---|---|---|
Documents | 用户生成的数据 | 是 |
Library/Caches | 可重新生成的缓存 | 否 |
Library/Preferences | UserDefaults | 是 |
Library/Application Support | 应用支持文件 | 是 |
tmp | 临时文件 | 否 |
二、获取目录路径
获取 Documents 目录:
let documentsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
).first!
print(documentsURL.path)获取 Caches 目录:
let cachesURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
).first!获取 tmp 目录:
let tmpURL = FileManager.default.temporaryDirectory获取 Application Support 目录:
let supportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
).first!说明:
返回的是 URL 数组,通常取第一个。
userDomainMask表示用户目录。Application Support 目录可能不存在,需要手动创建。
三、文件路径拼接
使用 URL 拼接:
let documentsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
).first!
let fileURL = documentsURL.appendingPathComponent("notes.txt")
print(fileURL.path)推荐用 URL:
更安全,自动处理路径分隔符。
支持中文、空格等特殊字符。
配合 FileManager API 使用更方便。
避免手动拼接字符串:
// 不推荐
let path = documentsURL.path + "/notes.txt"
// 推荐
let fileURL = documentsURL.appendingPathComponent("notes.txt")四、写入文本文件
写入 String:
let content = "Hello, iOS!"
let fileURL = documentsURL.appendingPathComponent("greeting.txt")
do {
try content.write(to: fileURL, atomically: true, encoding: .utf8)
print("写入成功")
} catch {
print("写入失败:\(error)")
}参数说明:
to:目标文件 URL。atomically:是否原子写入,建议 true。encoding:编码格式,通常用.utf8。
原子写入的好处:
先写临时文件,再替换目标文件。
避免写入中途失败导致文件损坏。
五、读取文本文件
读取 String:
let fileURL = documentsURL.appendingPathComponent("greeting.txt")
do {
let content = try String(contentsOf: fileURL, encoding: .utf8)
print(content)
} catch {
print("读取失败:\(error)")
}读取失败常见原因:
文件不存在
编码不匹配
权限问题
路径错误
读取时判断文件是否存在:
if FileManager.default.fileExists(atPath: fileURL.path) {
let content = try? String(contentsOf: fileURL, encoding: .utf8)
print(content ?? "")
} else {
print("文件不存在")
}六、写入 Data
写入二进制数据:
let data = "Hello".data(using: .utf8)!
let fileURL = documentsURL.appendingPathComponent("data.bin")
do {
try data.write(to: fileURL)
print("写入成功")
} catch {
print("写入失败:\(error)")
}读取 Data:
do {
let data = try Data(contentsOf: fileURL)
if let content = String(data: data, encoding: .utf8) {
print(content)
}
} catch {
print("读取失败:\(error)")
}适用场景:
图片、音频等二进制文件
加密后的数据
序列化后的对象
七、JSON 文件读写
JSON 是 iOS 中常用的数据交换格式。
定义 Codable 模型:
struct Note: Codable {
var id: UUID
var title: String
var content: String
var createdAt: Date
}写入 JSON 文件:
let note = Note(
id: UUID(),
title: "第一条笔记",
content: "这是笔记内容",
createdAt: Date()
)
let fileURL = documentsURL.appendingPathComponent("notes.json")
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
let data = try encoder.encode([note])
try data.write(to: fileURL)
print("写入成功")
} catch {
print("写入失败:\(error)")
}读取 JSON 文件:
do {
let data = try Data(contentsOf: fileURL)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let notes = try decoder.decode([Note].self, from: data)
print(notes)
} catch {
print("读取失败:\(error)")
}日期编码策略:
策略 | 说明 |
|---|---|
| 默认,时间戳 |
| ISO 8601 字符串 |
| 秒级时间戳 |
| 毫秒级时间戳 |
八、文件管理操作
判断文件是否存在:
FileManager.default.fileExists(atPath: fileURL.path)删除文件:
do {
try FileManager.default.removeItem(at: fileURL)
print("删除成功")
} catch {
print("删除失败:\(error)")
}移动文件:
let newURL = documentsURL.appendingPathComponent("new.txt")
try FileManager.default.moveItem(at: oldURL, to: newURL)复制文件:
try FileManager.default.copyItem(at: sourceURL, to: destinationURL)创建目录:
let folderURL = documentsURL.appendingPathComponent("Notes")
do {
try FileManager.default.createDirectory(
at: folderURL,
withIntermediateDirectories: true
)
} catch {
print("创建失败:\(error)")
}说明:
withIntermediateDirectories: true会自动创建中间目录。目录已存在时不会报错。
列出目录内容:
do {
let contents = try FileManager.default.contentsOfDirectory(
at: documentsURL,
includingPropertiesForKeys: nil
)
for url in contents {
print(url.lastPathComponent)
}
} catch {
print("读取失败:\(error)")
}获取文件属性:
let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
let size = attributes[.size] as? Int64 ?? 0
print("文件大小:\(size) 字节")九、封装文件管理类
class FileStorage {
static let shared = FileStorage()
private let fileManager = FileManager.default
private init() {}
private var documentsURL: URL {
fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
}
func fileURL(for name: String) -> URL {
documentsURL.appendingPathComponent(name)
}
func save<T: Encodable>(_ object: T, to name: String) throws {
let url = fileURL(for: name)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try encoder.encode(object)
try data.write(to: url, options: .atomic)
}
func load<T: Decodable>(_ type: T.Type, from name: String) throws -> T {
let url = fileURL(for: name)
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(T.self, from: data)
}
func delete(_ name: String) throws {
let url = fileURL(for: name)
try fileManager.removeItem(at: url)
}
func exists(_ name: String) -> Bool {
let url = fileURL(for: name)
return fileManager.fileExists(atPath: url.path)
}
}使用:
let notes = [Note(id: UUID(), title: "标题", content: "内容", createdAt: Date())]
try FileStorage.shared.save(notes, to: "notes.json")
let loaded = try FileStorage.shared.load([Note].self, from: "notes.json")十、文件保护与安全
文件保护等级:
try (data as NSData).write(to: fileURL, options: .completeFileProtection)常用保护等级:
等级 | 说明 |
|---|---|
| 锁屏后完全不可访问 |
| 锁屏后已打开的可继续访问 |
| 首次解锁后可访问 |
| 无保护 |
适用场景:
用户敏感数据使用
.completeFileProtection后台任务需要访问的使用
.completeUntilFirstUserAuthentication
不建议直接存敏感数据:
密码、Token 使用 Keychain。
文件加密后再写入。
十一、使用注意事项
注意事项:
tmp 目录可能被系统清理。
Caches 目录也可能被清理。
重要数据放 Documents。
大文件不要放 UserDefaults。
十二、避坑指南
不要手动拼路径:用 URL 拼接。
写入要捕获异常:文件操作可能失败。
原子写入:避免文件损坏。
tmp 目录不持久:重要数据不要放 tmp。
Caches 会被清理:可重新生成的缓存才放这里。
JSON 日期格式:编码和解码策略要一致。
文件操作不要放主线程:大文件异步处理。
敏感数据加密:不要明文存储。
十三、检查清单
[ ] 理解 iOS 沙盒目录结构
[ ] 能获取 Documents、Caches、tmp 目录
[ ] 会使用 URL 拼接文件路径
[ ] 能读写文本文件
[ ] 能读写 Data
[ ] 能读写 JSON 文件
[ ] 会判断、删除、移动、复制文件
[ ] 会创建目录并列出内容
[ ] 能封装文件管理类
[ ] 了解文件保护等级