本篇目标

理解 Core Data 的基本概念,掌握数据模型、实体、上下文、增删改查操作,能用 Core Data 完成一个简单的数据存储模块。

一、Core Data 是什么

Core Data 是 Apple 提供的对象图管理与持久化框架,用于管理应用中的模型数据。

核心特点:

  • 面向对象,直接操作 Swift 对象

  • 支持复杂数据模型与关系

  • 提供数据校验、版本迁移

  • 与 SwiftUI 深度集成

  • 底层可基于 SQLite 存储

适合场景:

  • 需要结构化存储的数据

  • 数据量大、需要查询和排序

  • 对象之间有复杂关系

  • 需要版本迁移

不适合场景:

  • 简单键值(用 UserDefaults)

  • 少量文件(用文件读写)

  • 敏感信息(用 Keychain)

二、核心概念

主要组件:

组件

作用

NSManagedObjectModel

数据模型(实体定义)

NSPersistentStoreCoordinator

持久化协调器

NSManagedObjectContext

上下文,操作数据

NSPersistentContainer

容器,封装以上组件

NSManagedObject

数据对象(实体实例)

关系图:

PersistentContainer
├── ManagedObjectModel(数据模型)
├── PersistentStoreCoordinator(存储协调)
└── ViewContext(主上下文)
    └── ManagedObject(数据对象)

说明:

  • 数据模型定义实体和属性。

  • 上下文是操作数据的入口。

  • 容器封装了以上组件,简化初始化。

三、创建数据模型

Xcode 数据模型编辑器 - NoteEntity 的属性面板

步骤:

  1. 在 Xcode 中 File → New → File from Template

  2. 选择 Data Model

  3. 命名,如 Model.xcdatamodeld

  4. 点击 Add Entity 添加实体

  5. 添加属性和类型

示例实体:

实体名:Note

属性

类型

说明

id

UUID

唯一标识

title

String

标题

content

String

内容

createdAt

Date

创建时间

实体选项:

  • Codegen 选择 Class Definition(自动生成类)

  • 或 Manual/None(手动创建类)

推荐:初学选 Class Definition,Xcode 自动生成。

四、初始化 PersistentContainer

在 App 中初始化:

import SwiftUI
import CoreData

@main
struct MyApp: App {
    let container: NSPersistentContainer

    init() {
        container = NSPersistentContainer(name: "Model")

        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("加载失败:\(error)")
            }
        }

        container.viewContext.automaticallyMergesChangesFromParent = true
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, container.viewContext)
        }
    }
}

说明:

  • name 与数据模型文件名一致。

  • loadPersistentStores 加载存储。

  • viewContext 是主线程上下文。

  • 通过 environment 注入到视图。

SwiftUI 中使用:

struct ContentView: View {
    @Environment(\.managedObjectContext) private var context

    var body: some View {
        Text("内容")
    }
}

五、创建数据

使用 NSEntityDescription:

let note = Note(context: context)
note.id = UUID()
note.title = "第一条笔记"
note.content = "这是内容"
note.createdAt = Date()

do {
    try context.save()
    print("保存成功")
} catch {
    print("保存失败:\(error)")
}

说明:

  • Note(context:) 是自动生成的初始化器。

  • 修改属性后需要调用 save()。

  • 上下文会自动跟踪对象变化。

批量创建:

for i in 1...5 {
    let note = Note(context: context)
    note.id = UUID()
    note.title = "笔记 \(i)"
    note.content = "内容 \(i)"
    note.createdAt = Date()
}

try? context.save()

六、查询数据

使用 @FetchRequest(SwiftUI 推荐):

struct NoteListView: View {
    @Environment(\.managedObjectContext) private var context

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)],
        animation: .default
    )
    private var notes: FetchedResults<Note>

    var body: some View {
        List {
            ForEach(notes) { note in
                VStack(alignment: .leading) {
                    Text(note.title ?? "")
                        .font(.headline)
                    Text(note.content ?? "")
                        .font(.subheadline)
                        .foregroundColor(.gray)
                }
            }
        }
    }
}

使用 NSFetchRequest(代码中):

let request: NSFetchRequest<Note> = Note.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)]
request.fetchLimit = 10

do {
    let notes = try context.fetch(request)
    print("共 \(notes.count) 条")
} catch {
    print("查询失败:\(error)")
}

带条件查询:

let request: NSFetchRequest<Note> = Note.fetchRequest()
request.predicate = NSPredicate(format: "title CONTAINS %@", "iOS")
request.sortDescriptors = [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)]

let results = try? context.fetch(request)

常用 NSPredicate:

格式

说明

"title == %@"

等于

"title CONTAINS %@"

包含

"createdAt > %@"

大于

"title BEGINSWITH %@"

前缀

"title ENDSWITH %@"

后缀

"age BETWEEN {18, 30}"

范围

七、更新数据

直接修改属性:

let request: NSFetchRequest<Note> = Note.fetchRequest()
if let note = try? context.fetch(request).first {
    note.title = "新标题"
    try? context.save()
}

说明:

  • Core Data 对象是引用类型。

  • 修改属性后,调用 save() 持久化。

  • 未保存的修改在上下文中存在,但不写入磁盘。

八、删除数据

删除单条:

context.delete(note)
try? context.save()

在列表中删除:

ForEach(notes) { note in
    Text(note.title ?? "")
}
.onDelete { indexSet in
    indexSet.forEach { index in
        let note = notes[index]
        context.delete(note)
    }
    try? context.save()
}

批量删除:

let request: NSFetchRequest<NSFetchRequestResult> = Note.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)

do {
    try context.execute(deleteRequest)
    try context.save()
} catch {
    print("批量删除失败:\(error)")
}

九、排序与分页

排序:

request.sortDescriptors = [
    NSSortDescriptor(keyPath: \Note.createdAt, ascending: false),
    NSSortDescriptor(keyPath: \Note.title, ascending: true)
]

分页:

request.fetchOffset = 0
request.fetchLimit = 20

说明:

  • fetchOffset:跳过的记录数。

  • fetchLimit:返回的最大记录数。

  • 用于列表分页加载。

十、数据模型关系

一对多关系:

例如:分类(Category)与笔记(Note)。

  • Category 有 notes(to-many)

  • Note 有 category(to-one)

定义:

  • 在 Category 实体中添加 notes,类型为 To Many。

  • 在 Note 实体中添加 category,类型为 To One。

  • 设置反向关系。

使用:

let category = Category(context: context)
category.name = "工作"

let note = Note(context: context)
note.title = "会议记录"
note.category = category

try? context.save()

查询某分类下的笔记:

let request: NSFetchRequest<Note> = Note.fetchRequest()
request.predicate = NSPredicate(format: "category.name == %@", "工作")
let notes = try? context.fetch(request)

十一、数据校验

在数据模型中,可以为属性设置校验规则:

  • 最小长度

  • 最大长度

  • 最小值

  • 最大值

  • 是否可选

保存前校验:

do {
    try context.save()
} catch let error as NSError {
    print("校验失败:\(error.userInfo)")
}

手动校验:

if note.title?.isEmpty == true {
    print("标题不能为空")
}

十二、封装 Core Data 操作

class NoteRepository {
    private let context: NSManagedObjectContext

    init(context: NSManagedObjectContext) {
        self.context = context
    }

    func create(title: String, content: String) -> Note {
        let note = Note(context: context)
        note.id = UUID()
        note.title = title
        note.content = content
        note.createdAt = Date()
        return note
    }

    func fetchAll() -> [Note] {
        let request: NSFetchRequest<Note> = Note.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)]
        return (try? context.fetch(request)) ?? []
    }

    func update(_ note: Note, title: String) {
        note.title = title
    }

    func delete(_ note: Note) {
        context.delete(note)
    }

    func save() {
        if context.hasChanges {
            try? context.save()
        }
    }
}

使用:

let repo = NoteRepository(context: context)
let note = repo.create(title: "标题", content: "内容")
repo.save()

十三、避坑指南

  • 不要在错误线程操作上下文:viewContext 是主线程。

  • 修改后要 save:否则不会持久化。

  • @FetchRequest 自动刷新:数据变化时界面自动更新。

  • 批量删除要合并上下文:否则 @FetchRequest 不会刷新。

  • 实体属性可选性:String 属性建议用可选类型或提供默认值。

  • 数据迁移:模型变化时要配置迁移策略。

  • 不要存大文件:图片、音频用文件系统。

十四、检查清单

  • [ ] 理解 Core Data 的核心概念

  • [ ] 能创建数据模型和实体

  • [ ] 会初始化 PersistentContainer

  • [ ] 能创建、查询、更新、删除数据

  • [ ] 会使用 @FetchRequest

  • [ ] 会使用 NSPredicate 条件查询

  • [ ] 理解数据模型关系

  • [ ] 能封装 Core Data 操作

  • [ ] 了解数据迁移