SwiftData框架属性包装器@Relationship
SwiftData框架属性包装器@Relationship

SwiftData框架属性包装器@Relationship

@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、更直观地表达数据之间的关系。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注