学习目标
- 理解Core Data的基本概念和组件。
- 掌握如何在SwiftUI项目中集成Core Data。
- 学习如何定义数据模型(
NSManagedObject
)。 - 掌握如何使用
@FetchRequest
从Core Data中获取数据。 - 学习如何执行数据操作(增、删、改)。
- 了解
NSManagedObjectContext
和数据持久化。
学习内容
1. Core Data 简介
Core Data是Apple提供的一个框架,用于管理应用程序的Model层数据。它不是一个数据库,而是一个对象图管理框架,可以将对象持久化到磁盘(通常是SQLite数据库)。
1.1 Core Data 主要组件
NSManagedObjectModel
:数据模型,定义了实体(Entity
)、属性(Attribute
)和关系(Relationship
)。NSPersistentStoreCoordinator
:协调器,负责将数据模型与持久化存储(如SQLite文件)连接起来。NSManagedObjectContext
:管理对象上下文,是与Core Data交互的主要接口。它是一个“暂存区”,用于创建、修改、删除和获取NSManagedObject
实例。NSPersistentContainer
:在iOS 10+中引入,简化了Core Data堆栈的设置,封装了上述三个组件。NSManagedObject
:Core Data中的数据记录,是实体在内存中的表示。
2. SwiftUI 项目集成 Core Data
2.1 创建项目时启用Core Data
在Xcode创建新项目时,勾选“Use Core Data”复选框,Xcode会自动为你设置好Core Data堆栈。
2.2 [YourAppName]App.swift
中的设置
Xcode会自动生成一个PersistenceController
类(或类似名称),并在App
结构体中注入managedObjectContext
到环境中。
// Persistence.swift (Xcode自动生成)
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "YourAppName") // 替换为你的项目名
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
// YourAppNameApp.swift
import SwiftUI
@main
struct YourAppNameApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(
.managedObjectContext,
persistenceController.container.viewContext
)
}
}
}
3. 定义数据模型 (.xcdatamodeld
)
- 打开
.xcdatamodeld
文件。 - 点击“Add Entity”创建新实体(例如
Task
)。 - 为实体添加属性(例如
name: String
,isCompleted: Bool
,timestamp: Date
)。 - 在右侧面板中,将
Codegen
设置为Class Definition
或Manual/None
,然后手动创建NSManagedObject
子类(如果选择Class Definition
,Xcode会自动生成)。
4. 获取数据 (@FetchRequest
)
@FetchRequest
属性包装器用于从Core Data中获取数据,并在数据变化时自动更新视图。
import SwiftUI
import CoreData
struct TaskListView: View {
@Environment(.managedObjectContext) private var viewContext
// 获取所有Task实体,按timestamp降序排列
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \.timestamp, ascending: false)],
animation: .default)
private var tasks: FetchedResults<Task> // Task是你的NSManagedObject子类
var body: some View {
NavigationView {
List {
ForEach(tasks) {
task in
Text(task.name ?? "Unknown Task")
}
}
.navigationTitle("My Tasks")
}
}
}
5. 数据操作 (增、删、改)
5.1 增加数据
func addTask() {
withAnimation {
let newTask = Task(context: viewContext)
newTask.name = "New Task \(tasks.count + 1)"
newTask.isCompleted = false
newTask.timestamp = Date()
do {
try viewContext.save() // 保存到持久化存储
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
5.2 删除数据
func deleteTask(offsets: IndexSet) {
withAnimation {
offsets.map { tasks[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
// 在List中使用
List {
ForEach(tasks) {
task in
Text(task.name ?? "Unknown Task")
}
.onDelete(perform: deleteTask)
}
5.3 修改数据
直接修改NSManagedObject
的属性,然后保存上下文。
func toggleTaskCompletion(task: Task) {
task.isCompleted.toggle()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
// 在List中点击修改
List {
ForEach(tasks) {
task in
Button(action: { toggleTaskCompletion(task: task) }) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
Text(task.name ?? "Unknown Task")
}
}
}
}
6. NSManagedObjectContext
和数据持久化
viewContext
是主线程的NSManagedObjectContext
,用于UI操作。- 所有对
NSManagedObject
的修改都发生在NSManagedObjectContext
中。 - 调用
viewContext.save()
会将上下文中的更改写入持久化存储。 - 在多线程环境下,应为每个线程创建独立的
NSManagedObjectContext
,并通过perform
或performAndWait
方法进行操作,以避免线程安全问题。
7. 过滤数据 (NSPredicate
)
@FetchRequest
可以接受一个predicate
参数来过滤数据。
// 获取所有未完成的任务
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \.timestamp, ascending: false)],
predicate: NSPredicate(format: "isCompleted == %@", NSNumber(value: false)),
animation: .default)
private var incompleteTasks: FetchedResults<Task>
实践练习
- 构建一个简单的待办事项应用:
- 创建一个新的SwiftUI项目,并启用Core Data。
- 定义一个
TodoItem
实体,包含title
(String) 和isCompleted
(Bool) 属性。 - 在
ContentView
中,使用@FetchRequest
显示所有待办事项。 - 添加一个
TextField
和一个Button
,用于添加新的待办事项。 - 为每个待办事项添加一个切换按钮,用于标记其完成状态。
- 实现滑动删除功能,删除待办事项。
- 数据过滤与排序:
- 在待办事项应用中,添加一个
Picker
或SegmentedPicker
,允许用户选择显示“所有任务”、“已完成任务”或“未完成任务”。 - 根据用户的选择动态修改
@FetchRequest
的predicate
。 - 尝试添加按标题字母顺序排序的功能。
- 在待办事项应用中,添加一个
思考题
- Core Data和UserDefaults、FileManager、SQLite等其他数据存储方式有什么区别和优缺点?
- 在多线程环境下使用Core Data时,如何确保数据的一致性和线程安全?
- 如何处理Core Data中的数据迁移(当数据模型发生变化时)?