在传统方式中,需要手动维护数据的关联性,比如通过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。
