Core Data 是什么、适合什么
Core Data 不是简单的"SQLite 封装",它是一个对象图管理框架:你操作的是 Swift 对象,Core Data 负责在合适的时机把它们写进磁盘,并维护对象之间的关系、校验与懒加载。对需要结构化记录、有对象关系、要查询排序的本地数据,Core Data 是苹果生态的默认选择。
判断用不用 Core Data:只有几个开关状态,用 UserDefaults 就够;一旦数据成"表"、要查要排序要有关系,就上 Core Data。
它的学习曲线偏陡,概念多,但把核心几个对象弄清楚后,日常增删改查其实很直观。
持久化容器与托管上下文
整个栈的入口是 NSPersistentContainer,它负责加载持久化存储并提供一个托管上下文(managedObjectContext)。你在上下文里增删改对象,调用 save() 时才真正写盘。
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores { desc, error in
if let error = error {
fatalError("加载持久化存储失败:\(error)")
}
}
let context = container.viewContext // 主线程上下文,供 UI 使用
- NSPersistentContainer容器,加载存储、提供上下文。
- NSManagedObjectContext上下文,对象的工作区,改动在这里发生。
- NSManagedObject托管对象,对应数据表里的一行。
- save()把上下文中的改动提交到磁盘。
建模:实体与关系
在 Xcode 的 Data Model 编辑器里画实体:新建一个 Entity,给它加属性(字符串、日期、数值),再设定主字段。编译后 Core Data 会生成对应的 NSManagedObject 子类。
实体之间还能建关系:一对多、多对多。比如一个 Folder 有很多 Note,这种关系在 Core Data 里是真正的对象引用,取 folder.notes 就是它下面的所有笔记,不用自己写 join 查询。
增删改查与 FetchRequest
查询用 NSFetchRequest:指定实体、排序、谓词(过滤条件),然后让上下文执行。新增就是在上下文里 insert 一个新对象,赋值后 save。
// 查询:按更新时间倒序取笔记
let req: NSFetchRequest<Note> = Note.fetchRequest()
req.sortDescriptors = [NSSortDescriptor(key: "updatedAt", ascending: false)]
let notes = try? context.fetch(req)
// 新增
let note = Note(context: context)
note.title = "新笔记"
note.createdAt = Date()
try? context.save()
谓词 NSPredicate 用来做条件过滤,例如"只查未完成的"。复杂查询记得加索引,并控制一次 fetch 的条数,别把几万条对象全拉进内存。
与 SwiftUI 结合
SwiftUI 用 @FetchRequest 把查询直接接到视图上:数据一变,列表自动刷新。容器通过 environment 注入,整个 app 共享同一份存储。
struct NoteList: View {
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Note.updatedAt, ascending: false)]
) var notes: FetchedResults<Note>
var body: some View {
List(notes) { note in
Text(note.title ?? "未命名")
}
}
}
这就是 Core Data 在 SwiftUI 里最舒服的姿势:声明一个 FetchRequest,剩下的增删改触发保存后,列表自动更新,不用手动刷新。
迁移与性能注意点
改了数据模型后,老用户升级可能触发迁移。轻量改动(加可选属性)通常用轻量自动迁移;大改则要建映射模型做自定义迁移。上线前务必用旧版本真实数据演练升级。
- 大批量操作放后台上下文,避免卡界面。
- 及时 save,上下文里堆太多未提交改动会变慢。
- 列表只 fetch 需要的字段与条数,配合分批。
- 在主线程只用 viewContext,后台任务用新的后台上下文。
实战:一个笔记的增删查
把增删查串起来:列表展示、新增按钮、左滑删除。
struct NoteList: View {
@Environment(\.managedObjectContext) private var context
@FetchRequest(sortDescriptors: [SortDescriptor(\.updatedAt, order: .reverse)])
private var notes: FetchedResults<Note>
private func add() {
let note = Note(context: context)
note.title = "笔记 \(notes.count + 1)"
note.createdAt = Date()
try? context.save()
}
private func delete(_ offsets: IndexSet) {
for i in offsets { context.delete(notes[i]) }
try? context.save()
}
var body: some View {
NavigationStack {
List {
ForEach(notes) { note in Text(note.title ?? "") }
.onDelete(perform: delete)
}
.toolbar { Button("加", action: add) }
}
}
}
整个流程没有一处手动刷新:save 之后 @FetchRequest 自动重查、界面自动更新。这正是 Core Data 与 SwiftUI 结合的顺滑之处。配合上一篇的并发知识,就可以在后台上下文做导入,再回到主线程更新界面。