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

SwiftData框架属性包装器@Relationship

在传统方式中,需要手动维护数据的关联性,比如通过id的方式绑定模型,通过id查询关联数据,任何更新或删除操作都可能导致数据的不一致。

@Relationship 是 SwiftData 框架中的属性包装器,用于在模型之间建立和管理关联关系。通过@Relationship,可以直接访问关联数据,不再需要通过id查询,SwiftData 自动维护两个对象之间的双向引用,减少维护复杂性和潜在错误。

基本概念

1、导入要求

import SwiftData

2、基本用法

@Model 模型的字段前添加 @Relationship,表示关系字段。

@Relationship
var records: [SavingsRecord] = []

3、定义模型

例如下面的代码示例中,PiggyBank(存钱罐)通过records字段关联多个SavingsRecord(存取记录):

import SwiftData

@Model
class PiggyBank {
    var name: String
    @Relationship(deleteRule: .cascade)
    var records: [SavingsRecord] = []
}

@Model
class SavingsRecord {
    var amount: Double
    var date: Date
}

records字段是一对多的关系,表示一个存钱罐对应多条存取记录。

deleteRule: .cascade 表示当删除某个PiggyBank实例时,SwiftData会自动删除与之关联的所有SavingsRecord对象。

注意:在实际项目中,@Model需要初始化器,否则会报错

4、使用示例

let piggyBank = PiggyBank(name: "生活")
let record = SavingsRecord(amount: 100, date: Date())
piggyBank.records.append(record)

创建一个PiggyBank存钱罐和SavingsRecord存取记录实例。

当删除piggyBank存钱罐时,SavingsRecord会被级联删除。

// 删除 piggyBank 存钱罐时,record 自动被级联删除
context.delete(piggyBank)

删除规则

deleteRule参数控制删除源对象时关联对象的影响。

1、.cascade(级联删除)

删除源对象时,自动删除所有关联对象。

@Model
class Department {
    var name: String
    
    // 删除部门时,员工的 department 属性变为 nil
    @Relationship(deleteRule: .nullify)
    var employees: [Employee] = []
}

@Model
class Employee {
    var name: String
    var department: Department?
}

适用于公司-员工、文章-评论,属于强依赖关系。

注意:在ViewModel中删除对象时,可能导致级联删除失效,需要手动调用context.fetch,刷新上下文并触发级联删除。

2、.deny(拒绝删除)

如果存在关联对象,拒绝删除源对象。

@Model
class Author {
    var name: String
    
    // 只有当作者没有书籍时才能删除
    @Relationship(deleteRule: .deny)
    var books: [Book] = []
}

@Model
class Book {
    var title: String
    var author: Author?
}

适合保护重要数据,需要手动清理关联的场景。

注意:实际测试发现,.deny并不能正常的工作,可能是我的配置出现了问题,类似问题请见Apple论坛。如果有这个需求,建议在删除前对关联字段进行判断,如果关联字段为空,则允许删除。如果不为空,则不允许删除。

3、 .noAction(无操作)

删除源对象时不做任何处理,需要手动管理关系。

@Relationship(deleteRule: .noAction)
var posts: [Post] = []

4、.nullify(无效)

删除源对象时,将关联对象的关系置为nil。

@Relationship(deleteRule: .nullify)
var posts: [Post] = []

如果不指定 deleteRule,SwiftData 默认规则通常是 .nullify。

如果不指定 inverse,则关系是单向的,不会自动同步。

测试发现 .noAction 和 .nullify 一样,都会将关联对象置为 nil,而不是保留原有的关系,可能是SwiftData内部自动保持数据完整性。

反向关系

在SwiftData中,如果没有反向关系,那么就是一个单向关系,

inverse定义关系的反向关联,指定关系的另一端如何指向当前模型,避免重复建模或歧义。

@Model
class Parent {
    var name: String
    
    // 使用 inverse 明确指定反向关系
    @Relationship(deleteRule: .cascade, inverse: \Child.parent)
    var children: [Child] = []
}

@Model
class Child {
    var name: String
    var parent: Parent?
}

反向关系可以自动同步,修改一端时会自动更新另一端,避免重复的数据库查询,防止关系不匹配。

inverse只需要在一端模型中使用,关联另一端的字段。如果两个模型都使用inverse进行关联:

// PiggyBank
@Relationship(deleteRule: .cascade,inverse: \SavingsRecord.piggyBank)
var records: [SavingsRecord] = []
// SavingsRecord 
@Relationship(inverse: \PiggyBank.records)
var piggyBank: PiggyBank?

Xcode会报错:

Circular reference resolving attached macro 'Relationship'

报错信息表示循环引用解析附加宏“关系”,因此inverse只需要在“集合一侧”中使用。

// 建议在“集合一侧”写inverse
@Model
class PiggyBank {
    var name: String
    @Relationship(deleteRule: .cascade,inverse: \PiggyBank.records)
    var records: [SavingsRecord] = []
}
// 
@Model
class SavingsRecord {
    var amount: Double
    var date: Date
    var piggyBank: PiggyBank?
}

注意:需要使用KeyPath语法指定反向属性。

// 使用 KeyPath 指定反向属性
inverse: \Child.parent

inverse在实际测试(简单的项目)中,并没有测试出实际的作用,deleteRule并不依赖inverse。可能是项目比较简单,SwiftData可以自动推断。在复杂项目中,建议显式写inverse(增加可读性、性能更好)。

高级配置

1、minimumModelCount 和 maximumModelCount

限制关系中对象的数量。

@Model
class Team {
    var name: String
    
    // 团队必须有 1-10 名成员
    @Relationship(
        deleteRule: .deny,
        minimumModelCount: 1,
        maximumModelCount: 10
    )
    var members: [Member] = []
}

适用于业务规则验证、容量限制等场景。

2、原始值(originalValue)

在某些情况下,可能需要访问关系的原始值:

// 访问未提交的更改前的值
let originalChildren = parent.children.originalValue

3、集合操作

关系支持标准的 Swift 集合操作:

// 添加
parent.children.append(newChild)

// 删除
parent.children.remove(at: index)

// 过滤
let activeChildren = parent.children.filter { $0.isActive }

// 映射
let childNames = parent.children.map { $0.name }

4、originalName、hasModifier、其他Schema选择

日常使用较少,牵扯到底层 schema 名称、hash 行为、迁移兼容等进阶场景。通常只在自定义迁移或非默认映射时需要关注。

5、@Attribute / Schema.Relationship.Option(唯一性等)

关系也可以配合其他 Schema/Attribute 选项使用(例如某些约束确保关系项唯一等)。详见 Schema 文档。

注意事项

1、明确指定删除规则

// 好的做法
@Relationship(deleteRule: .cascade, inverse: \Comment.post)
var comments: [Comment] = []

// 避免依赖默认值
@Relationship
var comments: [Comment] = []

2、始终定义反向关系

使用 inverse 定义反向关联关系,确保双向数据同步。

@Relationship(inverse: \PiggyBank.records)
var piggyBank: PiggyBank?

3、避免循环依赖

如果模型间关系复杂(如多对多),设计时要注意避免循环依赖。

// 使用 weak 或合适的删除规则避免循环引用
@Relationship(deleteRule: .nullify, inverse: \Node.parent)
var children: [Node] = []

4、默认删除规则

如果未设置 deleteRule,SwiftData 会默认采用 .noAction。

@Relationship
var records: [SavingsRecord] = []

总结

@Relationship 是SwiftData中描述实体关系的核心工具。用于声明模型之间的关系,使得它们可以通过对象直接导航,而无需手动管理标识符(例如 id)。

通过双向引用和自动管理的方式,确保两个数据模型之间的关系始终一致,通过关系直接访问目标对象或集合,不需要手动维护。

这种设计简化了复杂的数据库操作,并提供了更强的数据一致性保证。

相关文章

1、SwiftData模型属性Attribute:https://fangjunyu.com/2024/11/04/swiftdata%e6%a1%86%e6%9e%b6%e5%b1%9e%e6%80%a7%e5%8c%85%e8%a3%85%e5%99%a8attribute/

2、SwiftData报错@Model requires an initializer be provided for ‘PiggyBank’ (from macro ‘Model’):https://fangjunyu.com/2025/11/25/swiftdata%e6%8a%a5%e9%94%99model-requires-an-initializer-be-provided-for-piggybank-from-macro-model/

3、SwiftData .deny deleteRule not working:https://developer.apple.com/forums/thread/789084

4、SwiftData没有触发级联删除的BUG:https://fangjunyu.com/2025/11/24/swiftdata%e6%b2%a1%e6%9c%89%e8%a7%a6%e5%8f%91%e7%ba%a7%e8%81%94%e5%88%a0%e9%99%a4%e7%9a%84bug/

扩展知识

1、自动管理对象

如果没有显式使用@Relationship,SwiftData会尝试自动管理对象,并将对象字段指向管理字段。

@Model
class PiggyBank {
    var name: String
//    @Relationship(deleteRule: .cascade)
    var records: [SavingsRecord] = []
}

@Model
class SavingsRecord {
    var amount: Double
    var date: Date
    var piggyBank: PiggyBank?
}

当创建PiggyBank对象并插入SavingsRecord对象时,虽然没有设置@Relationship,但是SavingsRecord对象的piggyBank会指向PiggyBank。

当删除PiggyBank对象时,SavingsRecord的piggyBank字段为nil。

这是SwiftData的默认行为,它会尝试修复对象之间的引用,在简单模型中可以正常使用,但是在复杂场景中可能出错。

正确的做法是使用@Relationship并明确指定inverse和deleteRule。

   

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

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

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