Swift: Swift 文件与数据操作:FileManager 与 Codable

数据持久化是 App 的核心能力——从保存用户设置到缓存网络数据,掌握文件 I/O 和编码解码让你的数据在 App 重启后依然存在。

1. 你将学到


2. 一个独立开发者的真实故事

(1) 痛点:App 重启后数据全丢了

Alice 发布了她第一个 iOS 待办事项 App,用户反馈很好,但有一个致命问题:关闭 App 再打开,所有待办事项都消失了。用户在 App Store 打了一星差评。 `swift // 错误做法:数据只存在内存中 var todos: [TodoItem] = [ TodoItem(title: "买咖啡"), TodoItem(title: "写周报"), ] Alice 的待办数据存在数组里,App 一关闭,内存释放,数据就没了。

(2) Codable + FileManager 的解法

`swift struct TodoItem: Codable { let title: String var isDone: false } let encoder = JSONEncoder() if let data = try? encoder.encode(todos) { let url = FileManager.default.documentsDirectory.appendingPathComponent("todos.json") try? data.write(to: url) } 下次启动时从文件读取,数据永久保存。

(3) 收益:用户数据不丢失

维度 内存存储 文件持久化
App 重启 ❌ 数据丢失 ✅ 数据恢复
存储容量 RAM 限制 磁盘空间
数据共享 不支持 ✅ 可导出/备份
离线使用 不依赖 ✅ 完全离线
恢复速度 立即 ✅ 毫秒级

3. FileManager 文件管理

FileManager 是 Swift 的文件系统接口,提供创建、读取、移动、删除文件等操作。

(1) 常用目录

`mermaid graph TB A["FileManager.default"] --> B["documentsDirectory"] A --> C["cachesDirectory"] A --> D["temporaryDirectory"] B --> E["用户数据,会备份到 iCloud"] C --> F["缓存文件,系统可清理"] D --> G["临时文件,随时被删"]

目录 用途 是否备份 系统会清理
documentsDirectory 用户文档和数据 ✅ 会备份
cachesDirectory 缓存文件
emporaryDirectory 临时文件 ✅ 随时

▶ 示例:获取和创建目录

`swift // ============================================ // 获取常用目录路径并创建子目录 // ============================================ import Foundation let fm = FileManager.default // 1. 获取文档目录 let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first! print("文档目录: (docs.path)") // 2. 获取缓存目录 let caches = fm.urls(for: .cachesDirectory, in: .userDomainMask).first! print("缓存目录: (caches.path)") // 3. 创建子目录 let dataDir = docs.appendingPathComponent("MyAppData") if !fm.fileExists(atPath: dataDir.path) { try? fm.createDirectory(at: dataDir, withIntermediateDirectories: true) print("创建目录: (dataDir.path)") }

输出: ext 文档目录: /Users/alice/Library/Developer/.../Documents 缓存目录: /Users/alice/Library/Developer/.../Caches 创建目录: /Users/alice/Library/Developer/.../Documents/MyAppData

(2) 文件写入与读取

FileManager 提供文件操作的基础方法,配合 Data 的 write 和 init(contentsOf:) 进行文件读写。

▶ 示例:写入和读取文本文件

`swift // ============================================ // 文本文件的写入和读取 // ============================================ import Foundation let fm = FileManager.default let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first! // 1. 写入文本 let fileURL = docs.appendingPathComponent("note.txt") let content = "Hello, Swift File I/O!" try? content.write(to: fileURL, atomically: true, encoding: .utf8) print("写入成功: (fileURL.lastPathComponent)") // 2. 读取文本 if let readContent = try? String(contentsOf: fileURL, encoding: .utf8) { print("读取内容: (readContent)") } // 3. 检查文件是否存在 print("文件存在: (fm.fileExists(atPath: fileURL.path))") // 4. 获取文件属性 if let attrs = try? fm.attributesOfItem(atPath: fileURL.path) { let size = attrs[.size] as? Int ?? 0 print("文件大小: (size) bytes") }

输出: ext 写入成功: note.txt 读取内容: Hello, Swift File I/O! 文件存在: true 文件大小: 21 bytes


4. Codable 与 JSON

Codable 是 Swift 4.0 引入的协议组合(Encodable + Decodable),让自定义类型轻松支持 JSON 编解码。

(1) Codable 协议

`mermaid graph LR A["Codable"] --> B["Encodable: encode to data"] A --> C["Decodable: decode from data"] B --> D["JSONEncoder().encode(obj)"] C --> E["JSONDecoder().decode(Type.self, from: data)"] D --> F["Data → write to file"] E --> G["Data from file → typed object"]

协议 方法 作用
Encodable unc encode(to: Encoder) 将对象编码为 Data
Decodable init(from: Decoder) 从 Data 解码为对象
Codable 两者组合 可编码也可解码

▶ 示例:JSON 编码解码自定义对象

`swift // ============================================ // 使用 Codable 对 Person 进行 JSON 编解码 // ============================================ import Foundation struct Person: Codable { let name: String let age: Int let email: String } // 1. 编码对象为 JSON let alice = Person(name: "Alice", age: 28, email: "alice@example.com") let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let jsonData = try? encoder.encode(alice), let jsonString = String(data: jsonData, encoding: .utf8) { print("编码后的 JSON:") print(jsonString) } // 2. 从 JSON 解码为对象 let json = """ {"name":"Bob","age":35,"email":"bob@example.com"} """.data(using: .utf8)! let decoder = JSONDecoder() if let bob = try? decoder.decode(Person.self, from: json) { print("\n解码后的对象: (bob.name), (bob.age)岁") }

输出: ` ext 编码后的 JSON: { "name" : "Alice", "age" : 28, "email" : "alice@example.com" }

解码后的对象: Bob, 35岁 `

(2) 嵌套对象与自定义键

实际 JSON 通常有嵌套结构和不同的键名,Codable 通过 CodingKeys 和嵌套类型处理。

▶ 示例:复杂 JSON 解析

`swift // ============================================ // 嵌套对象 + 自定义 JSON 键名映射 // ============================================ import Foundation struct User: Codable { let id: Int let fullName: String let address: Address let tags: [String] // 自定义键名映射(JSON 的 snake_case → Swift camelCase) enum CodingKeys: String, CodingKey { case id case fullName = "full_name" case address case tags } } struct Address: Codable { let city: String let country: String } let json = """ { "id": 1001, "full_name": "Charlie Wang", "address": { "city": "Shanghai", "country": "China" }, "tags": ["developer", "swift"] } """.data(using: .utf8)! let decoder = JSONDecoder() if let user = try? decoder.decode(User.self, from: json) { print("User: (user.fullName) (ID: (user.id))") print("City: (user.address.city)") print("Tags: (user.tags.joined(separator: ", "))") }

输出: ext User: Charlie Wang (ID: 1001) City: Shanghai Tags: developer, swift


5. UserDefaults 与数据持久化

UserDefaults 适合存储少量用户偏好,自动关联 iCloud 同步,无需手动管理文件路径。

(1) UserDefaults 存储

存储类型 Swift 类型 UserDefaults 方法
整数 Int set(_:forKey:) + integer(forKey:)
浮点数 Double set(_:forKey:) + double(forKey:)
布尔值 Bool set(_:forKey:) + ool(forKey:)
字符串 String set(_:forKey:) + string(forKey:)
数组 [Any] set(_:forKey:) + rray(forKey:)
字典 [String: Any] set(_:forKey:) + dictionary(forKey:)
Data Data set(_:forKey:) + data(forKey:)

▶ 示例:保存和读取用户设置

`swift // ============================================ // UserDefaults 保存用户偏好设置 // ============================================ import Foundation let defaults = UserDefaults.standard // 1. 保存设置 defaults.set("Alice", forKey: "username") defaults.set(true, forKey: "isLoggedIn") defaults.set(3, forKey: "launchCount") defaults.set(23.5, forKey: "lastTemperature") // 2. 读取设置 let username = defaults.string(forKey: "username") ?? "Guest" let isLoggedIn = defaults.bool(forKey: "isLoggedIn") let launchCount = defaults.integer(forKey: "launchCount") let temperature = defaults.double(forKey: "lastTemperature") print("用户: (username)") print("登录: (isLoggedIn)") print("启动次数: (launchCount)") print("上次温度: (temperature)°C") // 3. 删除 key defaults.removeObject(forKey: "lastTemperature") print("删除后读取: (defaults.double(forKey: "lastTemperature"))")

输出: ext 用户: Alice 登录: true 启动次数: 3 上次温度: 23.5°C 删除后读取: 0.0

(2) 使用 Codable 保存复杂对象到 UserDefaults

UserDefaults 无法直接存储 Codable 对象,但可以存 Data——先编码再存储。

▶ 示例:存储自定义类型到 UserDefaults

`swift // ============================================ // Codable + UserDefaults 存储复杂对象 // ============================================ import Foundation struct Settings: Codable { var theme: String = "light" var fontSize: Int = 14 var enableNotifications: Bool = true } let defaults = UserDefaults.standard let encoder = JSONEncoder() let decoder = JSONDecoder() // 保存 var settings = Settings() settings.theme = "dark" settings.fontSize = 16 if let data = try? encoder.encode(settings) { defaults.set(data, forKey: "appSettings") print("设置已保存") } // 读取 if let data = defaults.data(forKey: "appSettings"), let loaded = try? decoder.decode(Settings.self, from: data) { print("主题: (loaded.theme)") print("字号: (loaded.fontSize)") print("通知: (loaded.enableNotifications)") }

输出: ext 设置已保存 主题: dark 字号: 16 通知: true


6. 完整示例:待办事项管理器

`swift // ============================================ // 完整示例:待办事项持久化管理器 // 功能:增删改查 + JSON 文件持久化 // ============================================ import Foundation // 1. 待办事项模型 struct TodoItem: Codable { let id: UUID var title: String var isCompleted: Bool let createdAt: Date init(title: String) { self.id = UUID() self.title = title self.isCompleted = false self.createdAt = Date() } } // 2. 待办管理器 class TodoManager { private var items: [TodoItem] = [] private let fileURL: URL init() { let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! fileURL = docs.appendingPathComponent("todos.json") load() } func add(title: String) { items.append(TodoItem(title: title)) save() } func toggle(id: UUID) { if let index = items.firstIndex(where: { .id == id }) { items[index].isCompleted.toggle() save() } } func showAll() { for item in items { let status = item.isCompleted ? "✅" : "⬜" print("(status) (item.title)") } } private func save() { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let data = try? encoder.encode(items) { try? data.write(to: fileURL) } } private func load() { if let data = try? Data(contentsOf: fileURL), let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) { items = decoded } } } // 3. 使用示例 let manager = TodoManager() manager.add(title: "学习 Swift Codable") manager.add(title: "写文件操作教程") manager.add(title: "发布 App 更新") manager.showAll() print("--- 已完成一项 ---") // 实际中这里会 toggle 某项

输出: ext ⬜ 学习 Swift Codable ⬜ 写文件操作教程 ⬜ 发布 App 更新 --- 已完成一项 ---


❓ 常见问题

Q UserDefaults 和文件存储怎么选?
A UserDefaults 适合 <100KB 的简单键值对(用户偏好、token);结构化数据或超过 100KB 用文件存储(JSON/数据库)。UserDefaults 性能差且不支持部分更新。
Q Codable 的 JSONEncoder 输出格式怎么控制? **A:设置 encoder.outputFormatting 为 .prettyPrinted(人类可读)、.sortedKeys(稳定顺序)、.withoutEscapingSlashes(减少转义)。 Q:文件路径用 String 还是 URL?
A 永远用 URL。URL 支持路径拼接 ppendingPathComponent、获取文件属性、跨平台兼容。String 路径容易出错且不支持这些操作。
Q try? 和 try! 在文件操作中怎么用?
A 文件操作大概率会失败(磁盘满、权限不足、文件不存在),永远用 ry? 或 do-catch。 ry! 一旦失败 App 直接崩溃。
Q FileManager 的 attributesOfItem 能获取什么信息?
A 文件大小、创建日期、修改日期、权限、所有者、是否是目录等。用 ttrs[.size]、ttrs[.creationDate] 等键取值。
Q iOS 的沙箱机制是什么?
A 每个 App 只能访问自己的沙箱目录(Documents、Caches、tmp),不能读写其他 App 的文件系统。通过 FileManager 获取的 URL 在沙箱范围内。

📖 小节

  • FileManager 提供文件系统操作接口,常用目录有 documents、caches、temporary
  • Codable(Encodable + Decodable)让自定义类型支持 JSON 编解码
  • JSONEncoder/JSONDecoder 支持格式化输出、自定义键名映射
  • UserDefaults 适合存储小型偏好设置,但不适合复杂或大量数据
  • 复杂对象可先编码为 Data 再存入 UserDefaults
  • 文件操作必须处理错误——磁盘满、权限不足等总是可能发生

📝 作业

  1. 基础题: 使用 FileManager 在 documentDirectory 下创建 "MyNotes" 目录,写入一个 greeting.txt 文件,内容为 "Hello, File I/O!",然后读取并打印。
  2. 进阶题: 创建一个 Book 结构体(含 title、author、year、isbn),使用 JSONEncoder 保存到文件,再读取并用 JSONDecoder 恢复,验证数据一致性。
  3. 挑战题: 实现一个简单的 KV 存储引擎 SimpleStorage,基于 JSON 文件,支持 set(key: String, value: Codable) 和 get(key: String) -> T? 泛型方法,数据以字典形式持久化到文件。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏