这一节,我们来了解如何表达 ENTITY 之间的关系。为了演示,我们创建下面这样的关系:

其中:
- Series 表示视频所在的系列,一个系列可以包含多个视频,这是一个一对多的关系。这种关系,Core Data 用一个双箭头指向关系中,多的一方;
- Episode 表示视频,一个视频只能属于一个系列,这是一个一对一的关系;
- History 表示某个账号的视频观看记录,一个记录只能对应一个视频,这也是一个一对一的关系;
Series 和 Episode
首先,选中 CoreData.xcdatamodeld,在其中创建一个表示视频系列的 ENTITY:Series:

它有三个属性:
- id:表示数据库中视频系列记录的 ID;
- title:表示视频系列的标题;
- summary:表示视频系列的简介;
其次,把之前创建的 Episode ENTITY 改成下面这样:

其中:
- id:表示数据库中该视频的 ID;
- seriesId:表示该视频所属的视频系列;
- title 和 summary 则表示视频的标题和简介;
创建好了这两个 ENTITY 之后,就可以创建它们之间的关系了。先选中 Series,点击 Relationships 下面的 +
,创建一个叫做 episodes 的关系,它的 Destination 设置成 Episode:

然后,再选中 Episode,给它创建一个 Destination 为 Series 的关系,并把它的 inverse 设置成 episodes:

这时,再回到 Series,就会发现之前创建的 episodes 也有了对应的 inverse:

接下来,为了表达 Series 到 Episode 一对多的关系,我们选中 Series 中的 episodes 关系,在 Show the Data Model inspector 中,把 Type 设置成 To Many:

另外,在上图中还可以看到,我们把 Delete Rule 设置成了 Cascade。这也是在创建关系时,一定要认真考虑的设置。它表示从 Core Data 中删除一个关系的 Source 时,对 Destination 采取的动作。Xcode 提供了几种不同的选项:
- Deny:表示只要关系的目标还存在,就禁止删除 Source;
- Nullify:表示把指向目标的关系的设置成
nil
,但是不从 Core Data 中删除 Destination; - Cascade:表示在 Core Data 中连同对应的 Destination 一并删除,对于我们的例子来说,当删除一个视频系列时,理应删除所有和它对应的视频,因此我们把 episodes 关系的这个选项设置成了 Cascade;
- No Action:表示什么都不做,绝大多数情况下,我们都不会使用这个设置;
当然,如果你还不是特别清楚这些选项的含义也完全没问题,只要对它们有个印象就好了,稍后,我们会专门用一节的内容通过代码详细和大家分享这些配置的效果。
另外,在 Episode 指回 Series 的关系里,我们可以把 Delete Rule 设置成 Nullify。表示删除视频后,Episode 到 Series 的引用就不存在了。完成这些设置之后,还是在菜单中选择 Editor / Create NSManagedObject Subclass...,这次,选中 Series 和 Episode 这两个 ENTITY:

这次,Xcode 会创建两个新文件:
- Series+CoreDataClass.swift:是表示 Series ENITTY 的类,它和上一节创建的
Episode
是类似的:
@objc(Series)
public class Series: NSManagedObject {
}
- Series+CoreDataProperties.swift:是
Series
中包含的属性,由于要表达一对多的关系,它看起来稍微复杂一些:
extension Series {
@nonobjc public class func fetchRequest()
-> NSFetchRequest<Series> {
return NSFetchRequest<Series>(entityName: "Series")
}
@NSManaged public var id: Int64
@NSManaged public var title: String?
@NSManaged public var summary: String?
@NSManaged public var episodes: NSSet?
}
// MARK: Generated accessors for episodes
extension Series {
@objc(addEpisodesObject:)
@NSManaged public func addToEpisodes(_ value: Episode)
@objc(removeEpisodesObject:)
@NSManaged public func removeFromEpisodes(_ value: Episode)
@objc(addEpisodes:)
@NSManaged public func addToEpisodes(_ values: NSSet)
@objc(removeEpisodes:)
@NSManaged public func removeFromEpisodes(_ values: NSSet)
}
可以看到,它包含了两个扩展。第一个扩展里,是表达一对多关系的属性,它是一个 NSSet
对象;第二个扩展里,是 Xcode 自动生成的,管理一对多关系的方法。
- Episode+CoreDataProperties.swift:是上一节创建过的,由于我们修改了 Episode ENTITY 的定义,在这里,会更新出对应的属性:
extension Episode {
/// The same as before...
@NSManaged public var id: Int64
@NSManaged public var seriesId: Int64
@NSManaged public var ofSeries: Series?
}
可以看到,指回 Series 的关系,是通过是一个 Series?
类型的属性表示的。至此,Series 和 Episode 的关系,就设置好了。
History 和 Episode
接下来,我们创建表示用户浏览历史的 ENTITY:

其中:
- id:表示数据库中视频历史记录的 ID;
- episodeId:表示该记录对应的视频 ID;
- progress:表示视频的观看进度;
- updatedAt:表示生成记录的时间;
一个视频,应该只有一个最新的历史浏览记录;一个历史浏览记录,也只应该对应一个视频的信息,因此它们之间,是一对一的关系。为了表达这种关系,我们先给 History 创建一个指向 Episode 的关系。Delete Rule 设置成 Nullify,Type 设置成 To One:

再给 Episode 添加一个指回 History 的关系:

完成后,用和之前同样的方式给 History 生成 NSManagedObject
的派生类。在 History+CoreDataClass.swift 里,History
的定义是这样的:
@objc(History)
public class History: NSManagedObject {
}
在 History+CoreDataProperties.swift 里,它的属性是这样的:
extension History {
@nonobjc public class func fetchRequest()
-> NSFetchRequest<History> {
return NSFetchRequest<History>(entityName: "History")
}
@NSManaged public var id: Int64
@NSManaged public var progress: Double
@NSManaged public var updatedAt: Date?
@NSManaged public var episodeId: Int64
@NSManaged public var ofEpisode: Episode?
}
可以看到,对于一对一的关系,生成的代码就简单多了,不会有一对多的那些用于批量管理关系的方法。在 History
里,指向 Episode
的关系是通过一个 Episode?
类型的属性表示的。而在更新后的 Episode+CoreDataProperties.swift 里,也会更新出来一个指回 History
的属性:
extension Episode {
/// The same as before...
@NSManaged public var history: History?
}
这样,History 和 Episode 的一对一关系,也就定义好了。
What's next?
以上,就是在 Core Data 中表达 ENTITY 关系的方法。创建好了 ENTITY 和 Relationships 之后,下一节,我们来看如何读写这种带有关系的 ENTITY。通过实际的代码,我们可以进一步加强对 Relationships 的理解。