Core Data 简介
Core Data 是什么
Core Data
是 iOS SDK 里的一个很强大的框架,允许开发者以面向对象的方式存储和管理数据。可以帮助建立代表程序状态的模型层,能将模型对象的状态持久化到磁盘。Core Data 使用包括实体和实体间的关系,关系是构筑实体(Entity)间相互联系的桥梁,它决定了一个实体如何对另一个实体产生影响。Core Data 中的每一个实体定义都对应着 SQLite 数据库中的一个表。因此,从底层实现的角度来看,Core Data 中的关系可以被看作是一种在不同表间建立联系和进行操作的机制。
Core Data 特点
Core Data 具有以下主要特点:
- 数据模型管理:Core Data允许开发人员定义数据模型,并提供了对数据模型的创建、编辑和版本控制的支持。
- 对象关系映射(ORM):Core Data提供了对象关系映射的功能,可以轻松地将数据模型映射到对象模型,使开发人员可以使用面向对象的方式来操作数据。
- 数据持久化:Core Data支持数据的持久化存储,在应用程序关闭后仍然可以保留数据,并且可以与不同的数据存储后端进行集成,包括SQLite、XML和二进制文件等。
- 自动化数据变更追踪:Core Data可以自动跟踪数据的变化,并为开发人员提供了便捷的方法来处理数据的更新和同步。
- 内置查询语言:Core Data提供了一种称为NSPredicate的内置查询语言,可以用于执行复杂的数据查询和过滤操作。
Core Data 结构
Managed Object Model
受管理的对象模型:这是Core Data中数据模型的表示,定义了应用程序中的实体、属性以及它们之间的关系。受管理的对象模型通常在Xcode中使用可视化工具编辑,并保存为.xcdatamodeld文件Managed Object
受管理的对象:受管理的对象是Core Data中的核心概念,它们对应于数据模型中的实体,可以包含各种属性和关系。开发人员可以通过受管理的对象来操作数据并进行持久化存储Managed Object Context
受管理的对象上下文:受管理的对象上下文是Core Data中用于管理受管理的对象的临时存储区域。它充当了受管理的对象的主要操作区域,包括创建、读取、更新和删除受管理的对象Persistent Store Coordinator
持久化存储协调器:持久化存储协调器负责协调数据在持久化存储介质(如SQLite数据库、XML文件等)中的存储和检索。它管理一个或多个持久化存储,确保数据在存储介质和受管理的对象模型之间的正确映射和转换Persistent Store
持久化存储:持久化存储是实际用来存储Core Data中数据的介质,可以是SQLite数据库、二进制文件、XML文件等。每个持久化存储都由持久化存储协调器管理
Core Data 关系类型
在 Core Data 的架构中,实体间关系的描述方式多样。从实体间的引用角度出发,这些关系大致分为单向关系和双向关系。
单向关系存在于当一个实体(A)引用另一个实体(B),但 B 不反向引用 A 的情形。尽管在特定场景下,如 A 需要了解 B 的信息而 B 不需要知道 A 的详情时,单向关系可以满足要求,但考虑到数据的完整性和对象图的维护,双向关系往往是更优选择。
双向关系则是指当一个实体(A)引用另一个实体(B),同时 B 也反向引用 A 的情况。这种关系使得 Core Data 能够更有效地管理对象间的联结,并为开发者提供了从多个实体角度调用其他关联实体的更大灵活性。
进一步可以将关系划分为三种主要类型:
一对一(One-to-One)关系
1 2 3
定义: 一个实体(A)中的单个实例与另一个实体(B)中的单个实例相关联。 用途: 适用于两个实体间存在独特且直接的联系时。 示例: 个人(Person)与其护照(Passport)的关系。
一对多(One-to-Many)关系
1 2 3
定义: 一个实体(A)中的单个实例与另一个实体(B)中的多个实例相关联。 用途: 当一个实体能与另一个实体的多个实例建立联系时。 示例: 用户(User)与其发布的多条帖子(Posts)。
多对多(Many-to-Many)关系
1 2 3
定义: 一个实体(A)中的多个实例与另一个实体(B)中的多个实例相互关联。 用途: 适用于两个实体间的实例可以自由组合关联的场景。 示例: 文章(Article)与标签(Tag)之间的关系,其中一篇文章可以拥有多个标签,同时不同的文章也可以使用相同的标签进行标记。
Core Data逆向关系
当Core Data
建立双向关系后,框架会要求为这些关系指定逆向关系Inverse Relationship
。虽然苹果的官方文档明确要求必须设定逆向关系,但由于在许多情况下,即使未设置逆向关系,应用程序也能正常运行。
设置逆向关系的好处有:
- 保证数据完整性
- 帮助查询优化
- 高效管理内存
在 SwiftData 中,对于某些关系类型,即使开发者没有显式设置逆关系,SwiftData 也会自动在数据模型中补充逆关系信息。
Core Data删除规则
删除规则Delete Rules
在Core Data
使用中起着关键作用,它定义了在删除一个实体对象时如何处理与该对象关联的其他实体对象。选择合适的删除规则对于维护数据完整性和避免数据库中的悬空引用极为重要。
Core Data 提供了四种基本的删除规则:
Nullify
- 删除对象会导致所有相关联对象的对应关系属性被置为空。只是解除关系,并没有删除关联对象
Cascade
- 在删除对象时,所有与之关联的对象也会被删除。
Deny
- 若关联对象仍存在,则阻止删除操作。确保不会因删除一个对象而产生悬空引用。
No Action
- 删除对象时,不对关联对象做任何处理。可能会造成悬空引用,因此使用时需特别小心。
Core Data with CloudKit
随着 Core Data with CloudKit 普及,越来越多的开发者开始在应用中集成这项技术,以便为用户提供云存储和跨设备数据同步功能。在启用 Core Data with CloudKit 功能时,需要遵循一些特定的关系设置规则:
- 关系必须设为可选(
Optional
) - 必须定义逆向关系
- 不支持
Deny
的删除规则 - 不支持有序关系
在设计支持 CloudKit 的 Core Data 数据模型时,遵循这些规则非常重要,因为它们直接影响到云数据的同步机制和整个应用的数据完整性。
欲了解更多关于 Core Data with CloudKit 的细节,请参阅 “Core Data with CloudKit 系列文章”。
Core Data 使用
创建Core Data模型
打开项目的 CoreDataDemo.xcdatamodeld 文件,可以看到已经默认创建了一个名为 Item 的实体。
创建实体Entity
Entity是指描述数据模型的一部分,它代表了数据模型中的一个实体或对象。每个Entity通常对应于应用程序中的一个具体类型的数据,例如用户、产品、订单等。在Entity中定义了该实体的属性Attributes
和关系Relationships
,以便在数据库中存储和检索相关数据。通过定义Entity,可以帮助Core Data管理数据模型的结构,并支持数据的持久化存储和检索。
- 注意:创建 Entity 实体的首字母必须为大写。
创建属性Attributes
属性Attributes
是Entity
的一部分,用来描述实体对象的特征或数据。属性可以包括各种不同类型的数据,例如字符串、整数、浮点数、日期等。以下是关于Core Data属性的详细解释:
- 类型:每个属性都有一个特定的数据类型,用来定义属性可以存储的数据格式。
- 可选性:属性可以是可选的(Optional)或必需的(Required)。可选属性允许存储空值(nil),而必需属性要求必须有一个非空值。
- 默认值:可以为属性指定默认值,在创建实体对象时如果没有提供该属性的值,则会自动使用默认值。
- 唯一性:属性可以被标记为唯一的(Unique),以确保该属性的值在实体中是唯一的。
- 索引:可以为属性添加索引(Indexing),以提高查询该属性的性能。
- 转换器:通过使用属性的值转换器(Value Transformer),可以在属性的值与底层数据存储之间进行自定义转换。
- 校验规则:可以为属性添加校验规则(Validation Rules),以验证属性的值是否符合特定的条件或格式。
- 推导属性:除了常规属性外,还可以定义推导属性(Derived Property),它的值是通过其他属性计算得出的,而不是直接存储的。
- 双向绑定:Core Data支持属性之间的关系绑定(Binding),当一个属性的值发生变化时,可以自动更新相关联属性的值。
添加关系Relationship
Relationship(关系)用于描述实体之间的连接和关联。通过Relationship,可以在不同实体之间建立关系,从而在数据模型中表达出对象之间的联系。以下是关于Core Data Relationship 的详细解释:
- 类型:Relationship 可以是一对一(One-to-One)、一对多(One-to-Many)或多对多(Many-to-Many)关系。这种类型定义了每个实体之间的关联数量。
- 目标实体:每个 Relationship 都有一个目标实体,表示关系的另一端所指向的实体。这样可以建立起两个实体之间的连接。
- 反向关系:在 Core Data 中,每个 Relationship 都有一个对应的反向关系。通过反向关系,可以方便地在两个实体之间进行双向导航。
- 删除规则:可以为 Relationship 设置删除规则(Deletion Rules),以定义当某个实体被删除时如何处理与其相关联的实体。
- 延迟加载:Relationship 可以被设置为延迟加载(Lazy Loading),即只在需要时才从数据库中获取相关的数据。
- 级联操作:通过级联操作(Cascade)、空值置空(Nullify)、断开连接(Deny)、无效断开(No Action)等操作,可以控制在更新或删除实体时如何处理关联关系。
- 双向绑定:类似于属性之间的双向绑定,Relationship 也支持双向绑定,即当一个实体的关系发生变化时,相关联的实体也能自动更新。
- 递归关系:在 Core Data 中,可以定义递归关系(Recursive Relationship),即一个实体与自身建立关联,用于表示父子关系、树形结构等情况。
Core Data 默认生成的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extension Book {
@NSManaged public var id: String
@NSManaged public var name: String?
@NSManaged public var number: Int64?
@NSManaged public var borrowBy: Student?
}
extension Student {
@NSManaged public var id: String
@NSManaged public var name: String?
@NSManaged public var age: Int?
@NSManaged public var birthDay: Date?
@NSManaged public var score: Double?
@NSManaged public var borrow: Book?
}
Core Data 操作
NSPersistentContainer 初始化
NSPersistentContainer
是Core Data框架中引入的一个高级类,用于简化Core Data堆栈的创建和管理。以下是对NSPersistentContainer
的详细解释:
- 堆栈管理:
NSPersistentContainer
封装了Core Data堆栈的创建和管理过程,包括托管对象模型、持久存储协调器和托管对象上下文等。- 数据模型集成:它提供了便捷的方法来加载应用程序的数据模型文件(
.xcdatamodeld
),并将其编译为托管对象模型,以便进行实体和关系的定义。- 持久存储协调器:
NSPersistentContainer
内部包含一个持久存储协调器(persistent store coordinator),用于协调数据模型和持久存储之间的交互,并支持数据的持久化操作。- 托管对象上下文:通过
viewContext
属性,NSPersistentContainer
提供了一个主托管对象上下文,用于在UI层进行数据操作,以及一些后台托管对象上下文,用于处理后台任务。- 持久存储类型:
NSPersistentContainer
支持多种持久存储类型,包括SQLite、二进制文件、内存等,开发者可以根据需求选择适合的存储类型。- 后台任务支持:对于需要在后台执行长时间操作或大量数据处理的任务,
NSPersistentContainer
提供了便捷的方法来创建和管理后台托管对象上下文,以确保线程安全和性能。- 错误处理:
NSPersistentContainer
封装了许多与Core Data相关的错误处理逻辑,使得开发者能够更轻松地处理数据存储和检索过程中可能出现的错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// 在SceneDelegate文件中
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
/// 在AppDelegate文件中
lazy var persistentContainer: NSPersistentContainer = {
// 注意: 检查文件名是否正确
let container = NSPersistentContainer(name: "CoreDataDemo")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
可以封装管理类CDHelper
来管理,方便保存context
内的改动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import UIKit
import CoreData
final class CDManager {
static let shared = CDManager()
// MARK: - Core Data stack
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CoreDataDemo")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
return container
}()
func saveContext() {
let context = persistentContainer.viewContext
try? context.saveIfNeeded()
}
}
extension NSManagedObjectContext {
func saveIfNeeded(resetContext: Bool = false) throws {
if self.hasChanges {
do {
try self.save()
} catch let error as NSError {
#if DEBUG
assertionFailure("\(#function) throw failed, error: \(error.userInfo)")
#endif
throw error
}
if resetContext { self.reset() }
}
}
}
/// 在SceneDelegate文件中
func sceneDidEnterBackground(_ scene: UIScene) {
CDManager.shared.saveContext()
}
通过NSManagedObjectModel
获取所有Entry
1
2
3
4
5
6
7
8
func fetchAllEntry() {
let entities = persistentContainer.managedObjectModel.entities
for entity in entities {
for property in entity.properties {
print("Property: \(property.name)")
}
}
}
NSManagedObjectContext 实现增、删、改、查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 增
func insertBook(name: String) {
let context = persistentContainer.viewContext
guard let book = NSEntityDescription.insertNewObject(forEntityName: "Book", into: context) as? Book else { return }
book.id = UUID().uuidString
book.name = name
if context.hasChanges {
do {
try context.save()
print("Insert new book(\(name)) successful.")
} catch let error as NSError {
#if DEBUG
assertionFailure("\(#function) throw failed, error: \(error.userInfo)")
#endif
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 删
func deleteBook(with id: String) {
let context = persistentContainer.viewContext
let fetchBooks = NSFetchRequest<Book>(entityName: "Book")
fetchBooks.predicate = NSPredicate(format: "id = %@", id)
do {
try context.fetch(fetchBooks).forEach({ context.delete($0) })
if context.hasChanges {
try context.save()
}
} catch let error as NSError {
#if DEBUG
assertionFailure("\(#function) throw failed, error: \(error.userInfo)")
#endif
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 改
func updateBook(with id: String, name: String) {
let context = persistentContainer.viewContext
let fetchBooks = NSFetchRequest<Book>(entityName: "Book")
fetchBooks.predicate = NSPredicate(format: "id = %@", id)
do {
guard let book = try context.fetch(fetchBooks).first else { return }
book.name = name
if context.hasChanges {
try context.save()
}
} catch let error as NSError {
#if DEBUG
assertionFailure("\(#function) throw failed, error: \(error.userInfo)")
#endif
}
}
NSPredicate 查询
NSPredicate是用于描述数据查询条件的类。它允许开发者定义一种逻辑表达式,用来过滤和检索存储在Core Data数据库中的数据。以下是关于NSPredicate的详细解释:
- 查询条件:NSPredicate用于描述数据的查询条件,可以包括比较操作、逻辑运算、正则表达式等,以便过滤出符合条件的实体对象。
- 属性匹配:可以使用NSPredicate来指定希望匹配的实体属性和对应的数值、字符串或其他类型的值,以实现根据属性值进行查询。
- 逻辑操作:NSPredicate支持逻辑操作符(AND、OR、NOT)以及比较操作符(=、!=、>, <等),可以组合多个条件来构建复杂的查询条件。
- 集合操作:除了基本的比较操作外,NSPredicate还支持集合操作,例如IN(在某个集合中)、BETWEEN(在某个范围内)等,以便更灵活地进行数据筛选。
- 动态查询:NSPredicate还支持在运行时动态构建和修改查询条件,使得开发者能够根据需要动态地调整查询逻辑。
- 排序和分页:通过将NSPredicate与NSSortDescriptor和fetch limit结合使用,可以实现对查询结果的排序和分页。
- 谓词编译器:Core Data提供了谓词编译器(Predicate Compiler),用于将NSPredicate转换为底层数据库所理解的查询语句,以实现高效的数据检索操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
func fetchBook(with predicate: NSPredicate) -> [Book] {
let context = persistentContainer.viewContext
let fetchBooks = NSFetchRequest<Book>(entityName: "Book")
fetchBooks.predicate = predicate
do {
return try context.fetch(fetchBooks)
} catch let error as NSError {
#if DEBUG
assertionFailure("\(#function) throw failed, error: \(error.userInfo)")
#endif
}
return []
}
总结
Core Data是一个功能强大的数据持久化框架,提供了丰富的功能和工具,帮助开发者轻松地管理数据模型、存储和检索数据,并有效地处理数据操作过程中的各种需求和挑战。总结一下Core Data的关键特点:
- 数据建模:Core Data允许开发者通过实体(Entity)、属性(Attributes)和关系(Relationships)来建立数据模型,描述数据之间的关系和结构。
- 持久化存储:Core Data提供了持久化存储机制,可以将数据存储在SQLite、二进制文件等不同类型的存储介质中,并支持自动数据迁移和版本控制。
- 高性能查询:通过NSPredicate和NSSortDescriptor等工具,Core Data支持高效的数据查询和检索操作,使得开发者能够轻松地从数据库中获取所需的数据。
- 内存管理:Core Data提供了强大的内存管理功能,包括托管对象上下文(Managed Object Context)、托管对象模型(Managed Object Model)等,帮助开发者更好地管理内存和数据对象。
- 并发处理:Core Data支持多线程并发处理,通过多个托管对象上下文来实现数据操作的并行执行,提高应用程序的性能和响应速度。
- 版本控制:Core Data支持数据模型的版本控制,允许开发者对数据模型进行更新和演变,同时保持数据的兼容性和一致性。
- 错误处理:Core Data提供了丰富的错误处理机制,包括错误码、NSError对象等,方便开发者处理数据操作过程中可能出现的异常情况。
- iOS集成:作为苹果官方框架,Core Data与iOS平台无缝集成,开发者可以方便地在iOS应用程序中使用Core Data进行数据管理和存储。