You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 14:40:36