如何通过FileManager正确保存与加载自定义类型Article的数组?
Swift中Codable对象数组的高效持久化解决方案
问题背景
自定义Article类并实现Codable协议,用@State var articles = [Article]()存储大量数据,尝试NSArray相关方法保存/加载失败,UserDefaults因数据体积大不适用,需要在应用正常终止或即将崩溃前完成数据持久化。
原Article类代码:
class Article: Identifiable, Hashable, Codable { static func == (lhs: Article, rhs: Article) -> Bool { lhs.title == rhs.title } var hashValue: Int { return self.id.hashValue } var id: UUID var title: String var text: String init(id: UUID, title: String, text: String) { self.id = id self.title = title self.text = text } }
原代码失效原因:NSArray.data(from:to:)依赖NSCoding协议,而Article仅实现Codable未实现NSCoding,导致无法正确编码解码。
优化方案
利用Swift原生Codable协议的编码器/解码器实现高效持久化,同时通过监听应用生命周期事件保证数据在关键节点保存。
1. 简化Article类的Hashable实现
Swift 5.1+支持自动合成Hashable与Equatable实现,只要所有存储属性均为Hashable类型,可删除手动实现的方法:
class Article: Identifiable, Hashable, Codable { var id: UUID var title: String var text: String init(id: UUID, title: String, text: String) { self.id = id self.title = title self.text = text } // 编译器自动合成Hashable与Equatable逻辑 }
2. 实现后台保存方法
用JSONEncoder将数组编码为二进制数据,写入文档目录,后台线程执行避免阻塞UI:
func saveArticles() { DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self, !self.articles.isEmpty else { return } let fileManager = FileManager.default guard let docsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let fileURL = docsDir.appendingPathComponent("articles.json") do { let encoder = JSONEncoder() // 可选:开启格式化方便调试,发布环境可移除 encoder.outputFormatting = .prettyPrinted let data = try encoder.encode(self.articles) try data.write(to: fileURL) } catch { print("保存失败:\(error.localizedDescription)") } } }
3. 实现加载方法
对应JSONDecoder从文档目录读取数据并解码为数组:
func loadArticles() { let fileManager = FileManager.default guard let docsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let fileURL = docsDir.appendingPathComponent("articles.json") guard fileManager.fileExists(atPath: fileURL.path) else { return } do { let data = try Data(contentsOf: fileURL) let decoder = JSONDecoder() articles = try decoder.decode([Article].self, from: data) } catch { print("加载失败:\(error.localizedDescription)") } }
4. 关键节点自动触发保存
监听应用生命周期通知,在进入后台或即将终止时自动保存,降低数据丢失风险:
// 在View的onAppear中注册通知 .onAppear { // 应用即将终止时保存 NotificationCenter.default.addObserver(forName: UIApplication.willTerminateNotification, object: nil, queue: .main) { [weak self] _ in self?.saveArticles() } // 应用进入后台时提前保存 NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { [weak self] _ in self?.saveArticles() } } .onDisappear { // 移除通知监听 NotificationCenter.default.removeObserver(self) }
补充说明
- 若数据体积极大,可替换
JSONEncoder为PropertyListEncoder,其对Swift原生类型的序列化效率更高; - 应用崩溃时系统无法保证代码执行,上述方案仅覆盖正常终止/后台场景,针对崩溃场景的捕获实现复杂度高且受系统限制,需谨慎使用;
- 保存时建议先写入临时文件,再替换目标文件,避免写入中断导致数据损坏。
内容的提问来源于stack exchange,提问作者Seungjun
相关产品推荐
相关产品推荐

