SwiftData relationship 的自动推断和显示声明

182 阅读3分钟

在 SwiftData 中,模型之间的关系可以推断出来,,也可以使用 @Relationship 宏显式的表达出来。一般来说,当你想要一个非默认配置时,你需要 @Relationship;但在很多时候你可以忽略它。

例如,我们有一个 Bank 类用来表示银行,有一个 Customer 类用来表示顾客,示例代码如下:

class Bank {
    var name: String
    var customers: [Customer]

    init(name: String, customers: [Customer]) {
        self.name = name
        self.customers = customers
    }
}

@Model
class Customer {
    var name: String
    var bank: Bank

    init(name: String, bank: Bank) {
        self.name = name
        self.bank = bank
    }
}

根据上面的代码,SwiftData 可以推断出以下的模型关系:

  • 每个银行可以有很多顾客(银行对顾客是一对多的)。
  • 每个顾客必须恰好属于某一个银行(顾客对银行是一对一的)。

但是,现在这两种关系是分开的:如果我们创建一个顾客并设置其银行属性,SwiftData 不会将该顾客添加到该银行的 customers 数组中,因为它不会自动推断出这种关系是双向的。

如果我们对 Customer 模型进行一个小的更改,我们才会得到一个属性关系推理:

@Model
class Customer {
    var name: String
    var bank: Bank?

    init(name: String, bank: Bank?) {
        self.name = name
        self.bank = bank
    }
}

唯一的变化是我们将银行标记为可选的,这意味着它可以是空的。这是出于安全原因,要理解原因,请考虑以下几点:

  • 如果我们在顾客和银行之间有关系,那么设置顾客的 bank 属性应该将其从银行的 customers 中添加或删除。
  • 同样,从银行的 customers 中增加或删除顾客也应该调整顾客的 bank 属性。
  • 那么,如果你把一个顾客从一所银行移走,而没有把他添加到另一所银行,会发生什么呢?

当我们将bank 定义为非可选参数时,那顾客就必须属于某个银行。如果我们试图打破这个规则,SwiftData 就会在我们的应用中引发崩溃,因为我们把它设置为无效状态。

所以,SwiftData 默认采用了唯一安全的方法:它只会在安全的情况下推断关系。即当它不会因为我们改变了数组而无意中触发崩溃的时候。

另一方面,一旦我们将 bank 属性设置为可选属性,这个危险就消失了:从 customers 数组中删除一个 Customer 只会将他们的 bank 属性设置为 nil,所以没有崩溃风险。

这里的规则很简单:如果一个关系可以安全地推断出来,SwiftData 就可以自动的推断出来。

但很多时候,这还不够,我们可以在两个模型之一上使用 @Relationship 宏创建显式关系。例如,我们可以改变Customer 类,将它的 bank 属性修改如下:

@Model
class Customer {
    var name: String
    @Relationship(inverse: \Bank.customers) var bank: Bank
    
    init(name: String, bank: Bank) {
        self.name = name
        self.bank = bank
    }
}

可以看到上面的代码将 bank 设置成了非可选参数。如果我们显示的使用了 @Relationship,那参数可以是可选的,也可以是非可选的。这里不存在二义性了,因为你明确告诉了 SwiftData 你预期的关系是什么。