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

SwiftData框架属性包装器@Relationship

@Relationship 是 SwiftData 框架提供的一个属性包装器,用于声明实体之间的关系。它是 SwiftData 中的核心功能之一,允许开发者在定义数据模型时,直观地描述和管理实体之间的关联。

@Relationship 的用途

定义数据模型之间的关联:

用于描述两个实体(比如 User 和 Job)之间的逻辑关系。

管理数据依赖:

定义数据在增删改查时的行为(例如级联删除)。

简化代码:

自动处理关联的存储、查询和关系维护,无需手动管理复杂的外键。

使用方式

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 管理关联对象的生命周期和行为。这种设计简化了复杂的数据库操作,并提供了更强的数据一致性保证。

完整的JobView代码

import Foundation
import SwiftData
import SwiftUI

struct JobView: View {
    @Environment(\.modelContext) var modelContext
    @Query var job: [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)
    }
    
    func deleteSample() {
        do {
            let users = try modelContext.fetch(FetchDescriptor<User>())
            for user in users {
                if user.name == "Piper Chapman" {
                    modelContext.delete(user)
                }
            }
        } catch {
            print("报错了")
        }
    }
    
    
    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)")
        }
    }
}

#Preview {
    JobView()
        .modelContainer(for: [User.self, Job.self])
}

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

发表回复

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