Core Data应用开发实践指南
上QQ阅读APP看书,第一时间看更新

4.1 关系

关系是用来链接实体的。在托管对象模型中使用关系,就相当于引入了一种强大的手段,用以把实体形式的数据中某些逻辑区域连接起来。使用关系可大幅降低数据库对容量的需求。假如不使用关系,那么同样的数据就可能会在多个实体中重复出现,而使用了关系之后,只需在存储区里保存一份数据即可,原有的重复数据可以用关系来取代,该关系就相当于指向这份数据的指针。消除重复确实是关系的一项优势,但其真正强大的地方则在于:它可以在复杂的数据类型之间建立连接。

以Grocery Dude模型中的Item实体与Unit实体为例。Item实体的实例用来表示可以添加到购物清单里的货品,比方说:

·Chocolate(巧克力)

·Potatoes(土豆)

·Milk(牛奶)

而Unit实体的实例则用来表示g(克)、Kg(千克)及ml(毫升)等计量单位,这些计量单位及其数值可以添加到item里面。比方说:

·250g chocolate(250克巧克力)

·4Kg potatoes(4千克土豆)

·500ml milk(500毫升牛奶)

当然可以在Item实体里面添加名为unit的属性,并把它的类型设为字符串。然后,针对每个item对象都生成一个字符串,其中写上某种计量单位,例如g、Kg、ml等。但这种做法的问题在于,它会浪费数据库的存储空间,因为在这些Item的unit属性里,保存着大量的重复数据。除了浪费空间这个缺点之外,假如要更新所有Item的unit,那也是件非常麻烦的事。比方说,现在要将计量单位从Kg改为kilogram,那就得遍历所有的Item,并逐个修改其中的字符串,把里面的Kg改成kilogram。这样做效率很低,而且还会导致应用程序运行得相当缓慢。解决办法是:用指向Unit实体的“关系”来取代这种字符串,该“关系”其实就是个指向Kg对象的指针,而数据库里只需保存一份这样的Kg对象就好。这不仅降低了数据所占用的存储空间,而且还有个好处,就是若要改动计量单位的名称,则只需修改这一个对象即可。

提示 为了继续构建范例程序,需要把上一章中的代码添加到Grocery Dude项目中。或者可以去http://www.timroadley.com/LearningCoreData/GroceryDude-AfterChapter03.zip下载ZIP文件,并将其解压缩,这个文件包含了项目到目前为止的全部内容。在学习本章的过程中,你最好能用iOS仿真器来运行程序,因为这样更容易查看到SQLite数据库文件里的内容。

请按下列步骤修改Grocery Dude,为本章范例程序做准备:

1.从iOS仿真器里面删掉Grocery Dude应用程序。

2.点击Product>Clean菜单项,确保以前的同名项目没有把缓存残留下来。

3.通过iOS仿真器运行一遍程序,以生成空的持久化存储区。

请按下列步骤修改Grocery Dude,以创建关系:

1.可以先抓取快照或备份整个项目。

2.用第3章所讲的办法,根据Model 4来创建新的模型版本,并将其命名为Model 5。

3.把Model 5设为Current Model(当前模型)。

4.选定Model 5.xcdatamodel。

5.把界面的Editor Style改为Graph,如图4-1所示。

6.如果Item与Unit实体相互重叠,那就通过拖曳鼠标将其分开。

7.按下Control键,用鼠标从Item实体向Unit实体拖一条线。正确的操作结果如图4-1所示。

图4-1 在两实体间创建名为newRelationship的关系

当编辑器界面处于Graph风格时,在两实体间创建的关系是双向关系,也就是说,两个实体之间的这种双向关系其实是由两条方向相反的关系所组成的。在本例中,一条关系是从Item实体指向Unit实体,而另外一条关系则是从Unit实体指向Item实体。假如在Table风格的编辑器界面中创建关系,那么创建出来的可能就是单向关系(one-way relationship)了。此时如果需要建立双向关系,那还必须手动添加反向的那一条关系才行。

设置好双向关系之后,可以执行下列操作:

1.将Item托管对象关联到Unit托管对象。

2.将Unit托管对象关联到Item托管对象。

把两个实体关联起来之后,我们就可以通过关系来访问相关实体的属性了(比方说item.newRelationship.name)。接下来需要考虑的问题则是:每个方向上的关系是一对多的,还是一对一的?想清楚这个问题之后,你应该就能给关系起一个更为恰当的名字了。一对多的关系不限制目标对象的数量,而一对一的关系则会把目标对象的数量限定为一个。

现在考虑从Item到Unit方向的关系:

·假设从Item实体到Unit实体的关系是一对多的,那就意味着每个货品(item)都可以拥有无数种计量单位。这不太合适,因为购物清单上的货品只需要一种计量单位就够了,比方说Kg(千克)或pound(磅)。请注意:也可以限定一对多关系所能关联的对象个数上限。

·如果从Item实体到Unit实体的“关系”是“一对一”的,那就意味着每个货品只能有一种计量单位。这比较合适,因为购物清单上的货品只需一种计量单位即可。由此我们觉得从Item到Unit方向的“关系”起名叫做unit是比较合适的。把“关系”名称由newRelationship改为unit之后,就可以在item对象上通过item.unit.name来引用相关计量单位的名称了。

由于本例中的“关系”是“双向关系”,所以我们要把两个方向都考虑到。

接下来,考虑从Unit到Item方向的“关系”:

·假设从Unit实体到Item实体的关系是“一对一”的,那就意味着每一种计量单位只能由一件货品来使用,多个货品无法共用一种计量单位。这不合适,因为购物清单上面的多项货品应该可以共用同一种计量单位才对,比方说,“两千克洋葱”和“一千克玉米”都应该能够使用“千克”这个单位。

·如果从Unit实体到Item实体的关系是“一对多”的,那就意味着每一种计量单位可以供无数件货品使用。这很合适,因为购物清单上的多件货品确实有可能使用同一种计量单位。由此我们觉得从Unit到Item方向的“关系”应该起名叫做items。如果想列出与某种计量单位有关的全部对象,那很简单,只需获取unit.items就行了,这个NSSet里面会有许多指针,而每个指针都指向一件使用该计量单位的货品。

请按下列步骤修改Grocery Dude,以便配置刚才创建出来的双向关系:

1.把从Item实体指向Unit实体的newRelationship改名为unit。

2.把从Unit实体指向Item实体的newRelationship改名为items。

3.把items这条关系的Type改成To-Many,如图4-2所示。

图4-2 unit关系和items关系

你现在应该已经明白了:关系的名称要和它所提供的某种访问能力相符。配置好unit及items这两条关系之后,我们就可以在代码里通过item.unit及unit.items来访问关系的目标对象所具备的各种特性了。但是,在使用“点”(.)来访问特性之前,我们必须先为这些实体创建相应的NSManagedObject子类。

请按下列步骤修改Grocery Dude,以更新现有的NSManagedObect子类:

1.确保Model 5.xcdatamodel处于选定状态。

2.用前面章节讲过的办法来为Item及Unit实体创建NSManagedObject子类。在Select the entities you would like to manage这一步里,记得勾选Item与Unit实体。保存类文件的时候,别忘了把Targets中的“Grocery Dude”选中。Xcode会提示是否覆盖现有文件,点击Replace按钮即可。

查看Unit.h文件,你会发现有个NSSet类型的items特性。这个items特性是用来表达“一对多关系”的,而由于是“一对‘多’”,所以特性的类型是NSSet,以便容纳多个元素,另外,NSSet还提供了一些辅助方法,用以添加及移除对象。值得注意的是:与NSOrderedSet或NSArray不同,NSSet里面的对象是无序的。获取对象时,如果要排序,那么通常会给获取请求传入排序描述符,而获取请求执行完之后所返回的是NSArray。假如在配置“一对多关系”的时候勾选了Ordered选项,那么在NSManagedObject子类里面,对应的特性类型就成了NSOrderedSet。另外,NSSet与NSArray之间还有个差别也值得注意,就是NSSet不能包含重复对象。

接下来查看Item.h文件,你会发现有个Unit类型的unit特性。由于从Item指向Unit的关系是一对一关系,因此系统只需要把这个关系的目标实体所对应的类当作unit特性的类型即可。

如程序清单4-1所示,我们很容易就能把两个item对象的unit属性都设为同一种计量单位。

程序清单4-1 AppDelegate.m文件中的demo方法(演示如何使用刚才配置好的关系)

请按下列步骤修改Grocery Dude,以插入使用同一种计量单位的多件货品:

1.修改AppDelegate.m文件中的demo方法,用程序清单4-1里的代码替换原有代码。

2.运行一遍应用程序。控制台里应该会出现如图4-3所示的日志。

3.删掉AppDelegate.m文件demo方法中的所有代码。

图4-3 插入多个item,这些item都通过关系指向同一种计量单位

我们采用第2章所讲的办法查看Grocery-Dude.sqlite文件的内容,以证明数据库里只有一行数据是用来表示Kg对象的。正常结果应该如图4-4所示。

图4-4 在SQLite数据库的ZUNIT表中存放的Unit实体

在学习下一节之前,请先关掉SQLite Database Browser。