@Relationship 是 SwiftData 框架提供的一个属性包装器,用于声明实体之间的关系。它是 SwiftData 中的核心功能之一,允许开发者在定义数据模型时,直观地描述和管理实体之间的关联。
@Relationship:概述与核心概念
在 SwiftData 中,@Relationship 是用来定义模型之间关系的属性包装器(Property Wrapper)。它的主要功能是自动管理对象之间的关联性,类似于数据库中的外键(Foreign Key)概念。
在数据模型中,不同的对象之间往往存在某种关系,例如:
一对一(One-to-One)
一对多(One-to-Many)
多对多(Many-to-Many)
@Relationship 通过明确的声明和底层的自动维护,使得这些关联变得高效、可靠和易于使用。
以存钱罐和存钱记录为例,当使用@Relationship时,可以关联存钱罐和存钱记录。
通过@Relationship,可以避免使用外键进行关联。
@Relationship 的主要特性
1、声明对象关系:
使用 @Relationship 声明两个模型之间的关系,使得它们可以通过对象直接导航,而无需手动管理标识符(例如 id)。
2、反向关系管理:
通过 inverse 属性,SwiftData 自动确保模型之间的关联关系是双向同步的。
3、自动维护完整性:
SwiftData 会在插入、更新或删除数据时,确保关系的完整性,不需要手动维护。
4、数据导航:
通过关系直接访问目标对象或集合,无需额外查询。
使用 @Relationship 的场景
一对多关系
以存钱罐和存钱记录为例:
一个存钱罐 PiggyBank 可以有多个存钱记录 SavingsRecord。
每个存钱记录 SavingsRecord 只属于一个存钱罐 PiggyBank。
@Model
class PiggyBank {
let id = UUID()
var name: String
@Relationship var records: [SavingsRecord] = [] // 一个 PiggyBank 包含多个 SavingsRecord
}
@Model
class SavingsRecord {
let id = UUID()
var amount: Double
@Relationship(inverse: \PiggyBank.records) var piggyBank: PiggyBank? // 一个 SavingsRecord 属于一个 PiggyBank
}
工作原理
1、@Relationship 的本质
@Relationship 背后的核心原理是数据模型之间的外键关系,由 SwiftData 自动管理。通过声明关系,SwiftData 会在底层创建一个关联映射,使得两个模型的数据始终保持一致。
正向关系:
例如,在 PiggyBank 中,@Relationship var records 表示它可以导航到多个 SavingsRecord。
反向关系:
在 SavingsRecord 中,@Relationship(inverse: \PiggyBank.records) 定义了一个反向关联,它指向所属的 PiggyBank。
SwiftData 会自动管理这两个方向的引用关系,确保它们始终一致。
2、数据一致性的维护
假设存在以下操作场景:
1、插入一个记录到存钱罐:
let piggyBank = PiggyBank(name: "Vacation Fund")
let record = SavingsRecord(amount: 100, piggyBank: piggyBank)
SwiftData 自动:
1、将 record 添加到 piggyBank.records。
2、将 piggyBank 设置为 record.piggyBank。
2、删除一个存钱记录:
piggyBank.records.removeAll { $0.id == record.id }
SwiftData 自动:
1、从 piggyBank.records 中移除 record。
2、将 record.piggyBank 设置为 nil。
3、数据持久化与查询
在使用 SwiftData 时,关系的操作实际上是在操作底层存储(SQLite 等)。以下是关键点:
PiggyBank 和 SavingsRecord 的关系会映射为数据库中的外键。
查询时可以利用关系快速获取目标对象,而无需手动构建复杂的查询。
示例:
@Query private var piggyBanks: [PiggyBank]
var firstBankRecords: [SavingsRecord] {
return piggyBanks.first?.records ?? []
}
SwiftData 会根据关系自动生成 SQL 查询,加载与 PiggyBank 相关的 SavingsRecord。
为什么需要绑定 @Relationship?
1、传统方式的问题
传统方式中,需要手动维护数据的关联性,比如通过 id 的方式:
手动绑定 SavingsRecord 的 piggyBankID。
在需要时通过 id 查询 PiggyBank 数据。
任何更新或删除操作都可能导致数据的不一致。
2、使用 @Relationship 的优势
1、避免手动查询:
通过 @Relationship,直接访问关联的数据,而不是通过 id 再去查询。
2、数据一致性:
SwiftData 自动维护两个对象之间的双向引用,减少维护复杂性和潜在错误。
3、简化代码:
操作关系时无需关心底层实现,直接操作对象属性即可完成更新。
使用 @Relationship 的注意事项
1、定义反向关系:
使用 inverse 定义反向关联关系,确保双向数据同步。
@Relationship(inverse: \PiggyBank.records) var piggyBank: PiggyBank?
2、关系的生命周期:
@Relationship 属性的生命周期与模型实例一致,因此要确保正确处理模型的插入和删除。
3、避免循环依赖:
如果模型间关系复杂(如多对多),设计时要注意避免循环依赖。
常见的使用方法和可选属性
在 SwiftData 中,@Relationship 是用于定义模型间关系的关键属性,它提供了一些配置方法和属性,允许开发者更灵活地管理数据模型的关系。以下是一些常见的使用方法和可选属性:
@Relationship 的核心功能
@Relationship 的核心功能是管理模型间的单向或双向关系。以下是 @Relationship 可用的主要属性:
主要属性
1、inverse
作用: 定义关系的反向关联。
类型: 可选,KeyPath
用途: 指定关系的另一端如何指向当前模型。
示例:
@Relationship(inverse: \PiggyBank.records)
var piggyBank: PiggyBank?
2、deleteRule
作用: 指定删除模型时如何处理其关联的对象。
类型: DeleteRule 枚举
枚举选项:
.cascade: 删除当前对象时,自动删除所有关联对象。
.nullify: 删除当前对象时,将关联对象的关系置为 nil。
.deny: 如果存在关联对象,禁止删除当前对象。
示例:
@Relationship(deleteRule: .cascade)
var records: [SavingsRecord]
3、minCount 和 maxCount
作用:限制关系中关联对象的数量。
类型:整数值
用途:指定关系必须满足的对象数量范围。
示例:
@Relationship(minCount: 1, maxCount: 5)
var savingsRecords: [SavingsRecord]
minCount: 1 表示关系至少需要一个关联对象。
maxCount: 5 表示关系最多可以关联五个对象。
4、optional
作用:指定关系是否可以为空。
类型:布尔值,默认为 true
用途:如果设置为 false,关系必须有一个有效的关联对象。
示例:
@Relationship(optional: false)
var piggyBank: PiggyBank
5、isToMany
作用: 指定关系是否为一对多。
类型: 布尔值
用途: SwiftData 会根据属性的类型(例如 [Type])自动推断是否为一对多关系,但可以通过此属性显式声明。
示例:
@Relationship(isToMany: true)
var records: [SavingsRecord]
6、isToOne
作用: 指定关系是否为一对一。
类型: 布尔值
用途: 与 isToMany 类似,但用于明确表示一对一关系。
示例:
@Relationship(isToOne: true)
var piggyBank: PiggyBank?
其他说明
1、组合属性
可以将多个属性组合在一个 @Relationship 声明中:
@Relationship(inverse: \PiggyBank.records, deleteRule: .cascade, optional: false)
var piggyBank: PiggyBank
2、默认行为
如果不指定 deleteRule,默认规则通常是 .nullify。
如果不指定 inverse,则关系是单向的,不会自动同步。
3、注意事项
@Relationship 定义的属性必须是可存储的(stored property)。
一对多关系需要使用数组类型 [Type]。
使用方式
1、一对多关系
import SwiftData
@Model
class User {
var name: String
var city: String
@Relationship(deleteRule: .cascade) var jobs = [Job]() // 一对多关系
init(name: String, city: String) {
self.name = name
self.city = city
}
}
@Model
class Job {
var name: String
var priority: Int
@Relationship var owner: User? // 多对一关系
init(name: String, priority: Int, owner: User? = nil) {
self.name = name
self.priority = priority
self.owner = owner
}
}
解释:
在 User 类中,@Relationship 指定了 jobs 是一组与 Job 相关的实例(多对一)。
deleteRule: .cascade 指定了当 User 被删除时,所有与之关联的 Job 也会被删除。
在 Job 类中,owner 是一个 User 实例的引用,描述了 Job 与其创建者的关系。
2、一对一关系
@Model
class Profile {
var bio: String
@Relationship(deleteRule: .nullify) var user: User?
init(bio: String, user: User? = nil) {
self.bio = bio
self.user = user
}
}
@Model
class User {
var name: String
var city: String
@Relationship var profile: Profile? // 一对一关系
init(name: String, city: String, profile: Profile? = nil) {
self.name = name
self.city = city
self.profile = profile
}
}
解释:
Profile 和 User 的关系是一对一。
deleteRule: .nullify 表示当 User 被删除时,不会删除 Profile,而是将其 user 属性设置为 nil。
3、多对多关系
@Model
class Student {
var name: String
@Relationship var courses: [Course] = [] // 多对多关系
init(name: String) {
self.name = name
}
}
@Model
class Course {
var title: String
@Relationship var students: [Student] = [] // 多对多关系
init(title: String) {
self.title = title
}
}
解释:
Student 和 Course 之间是多对多关系,每个学生可以选修多个课程,每个课程也可以被多个学生选修。
@Relationship 自动管理双向关系。
具体实例
@Model
class User {
var name: String
var city: String
var joinDate: Date
@Relationship(deleteRule: .cascade) var jobs = [Job]()
init(name: String, city: String, joinDate: Date) {
self.name = name
self.city = city
self.joinDate = joinDate
}
}
Model
class Job {
var name: String
var priority: Int
var owner: User?
init(name: String, priority: Int, owner: User? = nil) {
self.name = name
self.priority = priority
self.owner = owner
}
}
上面的代码是一对多的关系,一个User类对应多个Job类。
在视图当中,可以先定义一个@Query var job和临时储存User的属性。
@Query var job: [Job]
@State var modelUser: User?
新增一个创建按钮和删除按钮,并显示Job实例的数量:
var body: some View {
VStack {
Button(action: {
addSample()
}, label: {
Text("新增job数据")
.foregroundColor(Color.white)
.padding(10)
.background(Color.blue)
.cornerRadius(8)
})
Button(action: {
deleteSample()
}, label: {
Text("删除user1元素")
.foregroundColor(Color.white)
.padding(10)
.background(Color.blue)
.cornerRadius(8)
})
Text("目前jobs数量为:\(job.count)")
}
}
这两个按钮分别对应的是addSample()和deleteSample()方法。
当点击“新增job数据”按钮时,会调用addSample()方法,并创建一个User实例,在同时创建两个job实例:
func addSample() {
// 创建新的 User 和 Job
let user1 = User(name: "Piper Chapman", city: "New York", joinDate: .now)
let job1 = Job(name: "Organize sock drawer", priority: 3)
let job2 = Job(name: "Make plans with Alex", priority: 4)
modelContext.insert(user1)
user1.jobs.append(job1)
user1.jobs.append(job2)
}
在上下文中插入新增的user实例,并将两个job实例追加到user实例的jobs属性中。
这里实现的是一个user实例对应了两个job实例。前面在User类中提到,jobs使用的是@Relationship属性包装器:
@Relationship(deleteRule: .cascade) var jobs = [Job]()
这里的deleteRule: .cascade表示删除父对象时,会自动删除所有关联的子对象。
因此,当我们删除user实例时,对应的也会删除关联的两个job实例。
为了实现这一个一对多的关系以及删除的效果,首先点击“新增job数据”按钮。
可以发现,在视图中新增了一个user实例,并且显示当前的jobs数量为2。
也可以在数据库中看到新增的实例数据。
接着是点击删除按钮,并调用deleteSample()方法:
func deleteSample() {
do {
let users = try modelContext.fetch(FetchDescriptor<User>())
for user in users {
if user.name == "Piper Chapman" {
modelContext.delete(user)
}
}
} catch {
print("报错了")
}
}
deleteSample()方法会从modelContext上下文中找到user实例中名为“Piper Chapman”的实例并删除掉。
这时可以从视图或者数据库中看到,无论是user还是job实例都会被删除。
这就是前面提到的一对多关系,一个user对应多个job,当删除user时,对应的job也会删除。
删除规则(deleteRule)
@Relationship 提供了四种删除规则,用于控制当父对象被删除时,子对象应如何处理:
.cascade:删除父对象时,自动删除所有关联的子对象。
.nullify:删除父对象时,将子对象的关系置为 nil。
.deny:禁止删除父对象,除非所有子对象已被解除关系。
.noAction:删除父对象时,不对子对象执行任何操作(开发者需自行处理可能的孤立数据)。
优势
自动化: 通过简单的声明语句自动处理复杂的关系逻辑。
安全性: 删除规则可确保数据模型的完整性,避免孤立或无效数据。
直观性: 开发者可以轻松建模,符合日常逻辑理解。
注意事项
1、双向关系
必须在两个实体的关系中明确设置 @Relationship,才能使关系保持同步。
@Relationship var owner: User?
@Relationship var jobs: [Job]
2、默认删除规则
如果未设置 deleteRule,SwiftData 会默认采用 .noAction。
3、关系必须匹配
数据一致性由 SwiftData 自动管理,确保关系的正确性(如一对多、一对一等)。
总结
@Relationship 是 SwiftData 中描述实体关系的核心工具。通过它,开发者可以轻松地定义一对多、一对一、多对多等各种关系,并使用 deleteRule 管理关联对象的生命周期和行为。这种设计简化了复杂的数据库操作,并提供了更强的数据一致性保证。
如何理解 @Relationship 的工作原理
@Relationship 是 对象关系映射 (ORM) 的一种实现。
它通过 双向引用 和 自动管理 的方式,确保两个数据模型之间的关系始终一致。
使用它可以:
1、减少代码复杂性。
2、提高数据操作的安全性和可靠性。
3、更直观地表达数据之间的关系。