Core Data 教程:从零开始掌握 iOS 数据持久化
目录
- 什么是 Core Data? - 核心概念解析
- 为什么选择 Core Data? - 优势与适用场景
- 核心组件详解 - 理解 Core Data 的“五脏六腑”
- 实战演练:创建一个待办事项 App
- 步骤 1:创建项目与数据模型
- 步骤 2:生成 Core Data 代码
- 步骤 3:实现数据的增、删、改、查
- 步骤 4:绑定数据到 UI (UITableView)
- 步骤 5:完成增删改查的用户交互
- 进阶主题与最佳实践
什么是 Core Data?
Core Data 不是一个数据库,它是一个由 Apple 提供的强大、灵活的对象图管理和持久化框架。

你可以把它想象成一个智能的“对象管家”:
- 对象图管理:它能帮你管理你的 App 中所有对象之间的关系,一个
User对象可以关联多个Post对象,Core Data 会帮你维护这种复杂的关系网络。 - 持久化:它能将内存中的对象图自动保存到设备上(通常是 SQLite 数据库),并在下次启动 App 时重新加载回内存。
简单来说:你只需要操作普通的 Swift 对象(称为“托管对象”),Core Data 负责将它们与底层数据库进行同步,大大简化了数据存储和管理的复杂度。
为什么选择 Core Data?
- 面向对象:你操作的是 Swift 对象,而不是 SQL 语句,更符合 Swift 的编程范式。
- 高性能:它使用 SQLite 作为默认的存储方式,性能优异,它支持“ faulting”(惰性加载)机制,只在需要时才将数据加载到内存,节省资源。
- 关系支持:能轻松处理对象之间的一对一、一对多、多对多关系。
- 数据迁移:当你的数据模型(结构)发生变化时,Core Data 提供了强大的数据迁移工具,可以平滑地升级旧数据。
- 内置功能:提供撤销/重做、数据验证、加密等高级功能。
不适用场景:对于非常简单的键值对存储(如保存几个用户设置),使用 UserDefaults 更简单直接。
核心组件详解
理解这些组件是掌握 Core Data 的关键:

-
Managed Object Model (托管对象模型)
- 是什么:一个
.xcdatamodeld文件,它定义了你的数据结构,就像数据库的“表结构设计图”。 - 包含:
Entity(实体,相当于表)、Attribute(属性,相当于列,如name,age)、Relationship(关系,相当于表之间的关联)。
- 是什么:一个
-
Persistent Store Coordinator (持久化存储协调器)
- 是什么:Core Data 架构的“协调中心”。
- 作用:它负责连接托管对象模型和底层的持久化存储(如 SQLite 文件),它确保数据操作的一致性和安全性。
-
Managed Object Context (托管对象上下文)
- 是什么:你日常打交道最多的对象,它是一个“工作区”或“草稿板”。
- 作用:
- 你在上下文中创建、查询、修改托管对象。
- 这些修改最初只存在于内存中,尚未保存到磁盘。
- 当你调用
context.save()时,上下文会将所有更改“推送”给持久化存储协调器,最终写入数据库。 - 一个 App 通常只有一个主上下文。
-
Persistent Container (持久化容器)
- 是什么:从 iOS 10 开始引入的便利类,它将上述三个组件(
NSManagedObjectModel,NSPersistentStoreCoordinator,NSManagedObjectContext)封装在一起。 - 作用:极大地简化了 Core Data 的初始化过程,你只需要创建一个
NSPersistentContainer,它就能帮你管理好其他所有部分。
- 是什么:从 iOS 10 开始引入的便利类,它将上述三个组件(
-
Managed Object (托管对象)
- 是什么:由
Entity生成的 Swift 类(通常是NSManagedObject的子类),它代表你的数据模型中的一个具体实例。 - 例如:如果你的实体是
TodoItem,TodoItem类的一个实例就代表一个待办事项。
- 是什么:由
实战演练:创建一个待办事项 App
我们将创建一个简单的 App,可以添加、查看、编辑和删除待办事项。
步骤 1:创建项目与数据模型
-
在 Xcode 中,选择 "Create a new Xcode project"。
-
选择 "App" 模板,点击 Next。
-
填写产品名称(
TodoApp),选择 Team 和 Interface 为 Storyboard,Language 为 Swift。 -
勾选 "Use Core Data",Xcode 会自动为我们设置好 Core Data 的基本框架。
-
创建项目后,你会看到一个
CoreDataHelper.swift文件(或类似名称)和一个YourAppName.xcdatamodeld文件,我们先看.xcdatamodeld文件。 -
设计数据模型:
-
打开
TodoApp.xcdatamodeld。 -
点击 "Add Entity" 创建一个新实体,命名为
TodoItem。 -
选中
TodoItem实体,在右侧的 Data Model Inspector 中点击 "+" 添加两个属性:name: Type 为String, Optional 为false。isCompleted: Type 为Boolean, Optional 为false,Default Value 为false。
-
生成托管对象类:
- 选中
TodoItem实体,在菜单栏选择Editor -> Create NSManagedObject Subclass...。 - 选择语言为 Swift,Xcode 会自动生成
TodoItem.swift文件,这个文件就是我们的数据模型类。
- 选中
-
步骤 2:生成 Core Data 代码
我们来看 AppDelegate.swift,Xcode 已经为我们生成了一些代码,但为了更好地理解,我们来手动设置一遍。
-
在
AppDelegate.swift中,我们需要确保NSPersistentContainer被正确初始化,代码通常在application(_:didFinishLaunchingWithOptions:)方法中。 -
我们将创建一个便利的属性来访问
NSManagedObjectContext,这样在任何地方都能轻松获取。
修改 AppDelegate.swift 如下:
import UIKit
import CoreData
// ... 其他代码 ...
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Core Data Stack
// 持久化容器,是 Core Data 的核心
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "TodoApp") // "TodoApp" 是你的 .xcdatamodeld 文件名
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
// 便利属性,获取主上下文
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - App Lifecycle
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// ... applicationWillTerminate 等方法 ...
// MARK: - Core Data Saving
// 保存上下文的函数
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
解释:
persistentContainer: 创建并加载了我们的数据模型和存储。context: 一个只读计算属性,方便我们随时获取到主上下文。saveContext(): 一个封装了保存逻辑的函数,避免重复代码。
步骤 3:实现数据的增、删、改、查
现在我们来创建一个 DataManager 类来处理所有的数据操作,这样可以让 ViewController 更专注于 UI。
-
新建一个 Swift 文件
DataManager.swift。 -
编写
DataManager,让它持有AppDelegate的上下文。
import Foundation
import CoreData
class DataManager {
static let shared = DataManager()
private init() {} // 单例模式
// 从 AppDelegate 获取上下文
lazy var context: NSManagedObjectContext = {
(UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}()
// MARK: - CRUD Operations
// 创建 (Create)
func createTodoItem(name: String) -> TodoItem {
let newItem = TodoItem(context: context)
newItem.name = name
newItem.isCompleted = false
return newItem
}
// 读取 (Read) - 获取所有待办事项
func fetchTodoItems() -> [TodoItem] {
let fetchRequest: NSFetchRequest<TodoItem> = TodoItem.fetchRequest()
// 可以在这里添加排序,例如按创建日期排序
// fetchRequest.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
do {
return try context.fetch(fetchRequest)
} catch {
print("Error fetching data: \(error)")
return []
}
}
// 更新 (Update) - 通常在编辑时调用
func updateTodoItem(item: TodoItem, newName: String) {
item.name = newName
save()
}
// 删除 (Delete)
func deleteTodoItem(item: TodoItem) {
context.delete(item)
save()
}
// 保存
func save() {
if context.hasChanges {
do {
try context.save()
} catch {
print("Error saving context: \(error)")
}
}
}
}
CRUD 解释:
- Create: 创建一个新的
TodoItem实例,设置其属性,然后它会自动被添加到上下文中。 - Read: 使用
NSFetchRequest来定义一个查询请求,告诉 Core Data 你想要什么数据。context.fetch()执行这个请求并返回结果数组。 - Update: 直接修改从上下文中取出的
TodoItem对象的属性,这个修改是“暂存”在上下文中的。 - Delete: 调用
context.delete(),将对象标记为“待删除”。 - Save: 无论是创建、更新还是删除,都需要调用
save()或DataManager.save()来将上下文中的所有更改真正写入数据库。
步骤 4:绑定数据到 UI (UITableView)
- 打开
Main.storyboard,拖入一个UITableView到ViewController的视图中,并设置好约束。 - 为
UITableView创建一个 Outlet,命名为todoTableView。 - 为
ViewController设置UITableViewDataSource和UITableViewDelegate。
修改 ViewController.swift:
import UIKit
import CoreData
class ViewController: UIViewController {
@IBOutlet weak var todoTableView: UITableView!
var todoItems: [TodoItem] = []
override func viewDidLoad() {
super.viewDidLoad()
// 设置 tableView 的数据源和代理
todoTableView.dataSource = self
todoTableView.delegate = self
// 初始加载数据
fetchTodoItems()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 每次视图出现时都重新加载数据,确保数据是最新的
fetchTodoItems()
todoTableView.reloadData()
}
private func fetchTodoItems() {
todoItems = DataManager.shared.fetchTodoItems()
}
// MARK: - Navigation (点击 "+" 按钮跳转到添加页面)
// ...
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todoItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath)
let item = todoItems[indexPath.row]
cell.textLabel?.text = item.name
cell.accessoryType = item.isCompleted ? .checkmark : .none
return cell
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, completionHandler in
let itemToDelete = self.todoItems[indexPath.row]
DataManager.shared.deleteTodoItem(item: itemToDelete)
self.fetchTodoItems() // 重新获取数据
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
let completeAction = UIContextualAction(style: .normal, title: item.isCompleted ? "Undo" : "Done") { _, _, completionHandler in
let itemToToggle = self.todoItems[indexPath.row]
itemToToggle.isCompleted.toggle()
DataManager.shared.save()
tableView.reloadRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
completeAction.backgroundColor = item.isCompleted ? .systemOrange : .systemGreen
let configuration = UISwipeActionsConfiguration(actions: [completeAction, deleteAction])
return configuration
}
}
代码解释:
- 我们使用
DataManager.shared来获取数据。 fetchTodoItems()调用DataManager的方法,并将结果存入todoItems数组。UITableViewDataSource的两个方法使用todoItems数组来显示数据。UITableViewDelegate中的trailingSwipeActionsConfigurationForRowAt实现了左滑显示“完成”和“删除”按钮,并调用了DataManager的相应方法。
步骤 5:完成增删改查的用户交互
-
添加待办事项:
- 在 Storyboard 中,为
ViewController添加一个 "Add" Bar Button Item。 - 创建一个新的
UIViewController作为添加页面,包含一个UITextField和一个 "Save" 按钮。 - 在
ViewController中为 Bar Button Item 创建一个@IBAction,跳转到添加页面。 - 在添加页面的 "Save" 按钮的
@IBAction中,获取UITextField的文本,调用DataManager.shared.createTodoItem(name: text),然后保存并返回。
- 在 Storyboard 中,为
-
编辑待办事项:
- 在
ViewController中,为UITableView的行添加点击事件(didSelectRowAt)。 - 当用户点击一行时,可以跳转到编辑页面(可以复用添加页面的界面),并将当前
TodoItem的数据填充到UITextField中。 - 在编辑页面的 "Save" 按钮事件中,调用
DataManager.shared.updateTodoItem(...)来更新数据。
- 在
至此,一个完整的、支持增删改查的 Core Data App 就完成了!
进阶主题与最佳实践
- 后台上下文:对于耗时操作(如大量数据导入),应在后台队列上创建一个
NSManagedObjectContext,避免阻塞主线程,造成界面卡顿。let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateContext.parent = self.context // 设置主上下文为父上下文
- Fetched Results Controller:当数据需要实时反映到 UI 上时(比如数据变化时
UITableView自动更新),使用NSFetchedResultsController是最佳选择,它能帮你自动管理数据源和 UI 的同步。 - 数据迁移:当你的
.xcdatamodeld结构发生变化时(如新增一个字段),你需要创建一个“轻量级迁移”或“重量级迁移”的映射模型,以确保旧数据能正确转换到新结构。 - 错误处理:始终对
context.save()和context.fetch()等可能抛出异常的操作进行do-catch处理。 - 使用 SwiftUI:在 SwiftUI 项目中,可以使用
@Environment(\.managedObjectContext)来轻松获取 Core Data 上下文,并结合@FetchRequest属性包装器实现声明式的数据绑定。
Core Data 是一个功能极其强大的框架,虽然初看起来概念较多,但一旦掌握了其核心组件(Container, Context, Model),并通过实践建立起工作流,你就会发现它在处理复杂数据时的巨大优势。
本教程带你走完了从零到一构建一个 Core Data 应用的全过程,关键在于:
- 设计好数据模型。
- 正确初始化 Core Data 栈。
- 通过
NSManagedObjectContext管理对象的生命周期。 - 使用
NSFetchRequest进行数据查询。 - 别忘了保存!
希望这份教程对你有帮助!祝你编码愉快!
