1.4 为现有的应用程序添加Core Data支持
在Xcode中创建iOS应用程序项目时,可以使用各种起始模板(starting-point template)。假如要根据Master-Detail、Utility Application或Empty Application等模板来创建项目,那么只需勾选Use Core Data,即可在项目中使用Core Data。不过,Grocery Dude项目是根据Single View Application模板创建的,它起初并没有包含Core Data,笔者想通过这种方式来演示如何手工为项目添加Core Data支持,这样做更有意义。为了使用Core Data Framework,我们需要将它与项目相链接。
请按如下步骤修改Grocery Dude,以便将该项目与Core Data Framework相链接:
1.如图1-2所示,选中Grocery Dude Target。
2.在General分页中,点击Linked Frameworks and Libraries区域中的“+”按钮,然后把项目链接到CoreData.framework,如图1-2所示。
图1-2 将项目与Core Data Framework相链接
1.4.1 Core Data Helper简介
试着查看一下支持Core Data的那些内置模板,你也许会发现:Core Data是在应用程序委托(application delegate)里面设置的。本书所用的办法是通过辅助类来设置Core Data,这样的话,你就可以将这种办法运用到自己的项目中了。这也使得Core Data组件变得模块化,而且易于移植。我们将通过应用程序委托来惰性地(lazily)创建CoreDataHelper类的实例。这种实例可用来完成下列事项:
·初始化托管对象模型
·根据托管对象模型来创建持久化存储区,并据此初始化持久化存储协调器
·根据持久化存储协调器来初始化托管对象上下文
请根据下列步骤修改Grocery Dude,以便在新的Xcode组(group)里面创建CoreDataHelper类:
1.右击Xcode中的Grocery Dude组,然后创建名为Generic Core Data Classes的新组,如图1-3所示。
图1-3 在Xcode中创建名为Generic Core Data Classes的组
2.选定刚创建好的Generic Core Data Classes组。
3.点击File>New>File...。
4.新建iOS>Cocoa Touch>Objective-C class,然后点击Next按钮。
5.将Subclass of设为NSObject,并将Class的名称改为CoreDataHelper,然后点击Next按钮。
6.在Targets中勾选“Grocery Dude”,然后点击Create按钮,这样就会在Grocery Dude项目的目录中创建CoreDataHelper类了。
程序清单1-1列出了需要添加到CoreDataHelper头文件中的新代码。
程序清单1-1 CoreDataHelper.h
作为Objective-C程序员,你应该已经熟悉头文件(也就是.h文件)的用途了。CoreData-Helper.h用来声明CoreDataHelper中的context、model、coordinator及store等属性。当应用程序委托创建CoreDataHelper实例的时候,系统会调用setupCoreData方法。若要把托管对象上下文里发生的变更保存到持久化存储区,则可调用saveContext方法。假如要写入磁盘的数据比较多,那么该方法会导致用户界面卡顿。第11章将给程序添加后台保存功能,而在这之前,笔者建议你只从AppDelegate.m的appli-cationDidEnterBackground及applicationWillTerminate方法里面调用它。
请按照下述步骤修改Grocery Dude,以配置CoreDataHelper的头文件:
1.用程序清单1-1中的代码把CoreDataHelper.h文件里现有的全部代码都替换掉。假如现在查看CoreDataHelper.m文件,那么Xcode会提示你setupCoreData及saveContext方法还没实现,不过目前这无关紧要。
1.4.2 实现CoreDataHelper类
这个辅助类一开始会有四个主要的部分,它们分别叫做FILES、PATHS、SETUP及SAVING。为了便于查看及阅读代码,笔者用编译指示标记(pragma mark)将这些区域分隔开。编译指示标记使得程序员可以按照逻辑来组织源码,而且Xcode还会据此生成一份漂亮的菜单,以供用户在不同的部分中浏览。
图1-4 由编译指示标记所生成的菜单
1.4.3 FILES部分
CoreDataHelper.m的FILES部分中会有一个NSString,用于存储持久化存储区的文件名。如果稍后还要添加其他持久化存储区,那么也应该在这里写下它们的文件名。程序清单1-2中的代码包含一条#define语句,Grocery Dude中的绝大多数类都要使用这行语句,它是为了协助调试工作而设的。如果debug是1,那么该类的debug logging(调试日志记录)功能就会开启。本书大部分的NSLog命令都包裹在if(debug==1)语句里,所以只有开启了调试功能之后,那些命令才会有效果。
程序清单1-2 CoreDataHelper.m文件的FILES部分
请按下述步骤修改Grocery Dude,以便添加FILES部分:
1.把程序清单1-2中的代码添加到CoreDataHelper.m文件底部,但是要将其置于@end语句之前。
1.4.4 PATHS
为了把数据以持久化的形式写入磁盘,Core Data需要知道持久化存储文件在文件系统中的位置。我们可以分别编写3个方法来向Core Data提供这一信息。程序清单1-3列出了第一个方法,也就是applicationDocumentsDirectory方法,该方法返回NSString,而这个NSString就代表应用程序文档目录的路径。你会注意到我们这是首次把调试代码包裹在if(debug==1)语句里面,这种调试代码用于显示当前运行的究竟是哪个方法。NSLog语句很适合用来打印应用程序里各个方法的执行顺序,而这对调试工作很有帮助。
程序清单1-3 CoreDataHelper.m文件的PATHS部分
请按下列步骤修改Grocery Dude,以便添加PATHS部分:
1.将程序清单1-3中的代码添加到CoreDataHelper.m文件尾部,并放在@end语句之前。
接下来这个方法叫做applicationStoresDirectory,它会向应用程序文档目录中添加名为Stores的子目录,并且将其路径放在NSURL中返回。若Stores目录尚未建立,则程序清单1-4中的相关代码会将其创建出来。
程序清单1-4 CoreDataHelper.m文件中的applicationStoresDirectory方法
请按下列步骤修改Grocery Dude,以便将applicationStoresDirectory方法添加到PATHS部分中:
1.将程序清单1-4中的代码添加到CoreDataHelper.m文件尾部,并放在@end语句之前。
最后一个方法如程序清单1-5所示,它只是把持久化存储区的文件名添加到Stores目录的路径中。调用完该方法之后,我们就可以知道持久化存储文件的完整路径了。
程序清单1-5 CoreDataHelper.m文件中的storeURL方法
请按下列步骤修改Grocery Dude,以便将storeURL方法添加到PATHS部分中:
将程序清单1-5中的代码添加到CoreDataHelper.m文件尾部,并放在@end语句之前。
1.4.5 SETUP
处理完文件和路径之后,现在该实现初始化Core Data所用的三个方法了。程序清单1-6列出了第一个方法,它的名字叫做init,程序在创建CoreDataHelper实例的时候,会自动运行该方法。
程序清单1-6 CoreDataHelper.m文件的SETUP部分
_model实例变量指向NSManagedObjectModel对象。这个对象是我们在NSMana-gedObjectModel类上以nil为参数调用mergedModelFromBundles方法而得来的,该方法会用main bundle中的全部数据模型文件(data model file,也就是对象图)来初始化此对象。目前项目里还没有模型文件,但是第2章就会加进来一个。如果有多个模型需要合并,那么可以把元素类型为NSBundles的NSArray数组传给merged-ModelFromBundles,但一般来说不用担心这个问题。
提示 还有一种办法也能初始化托管对象模型,就是明确写出需要使用的模型文件。与直接合并bundle相比,这种写法的代码量多了一倍。可以用这条语句来手工指定模型:_model=[[NSManagedObjectModel alloc]initWithContents-OfURL:[[NSBundle mainBundle]URLForResource:@"Model"withExten-sion:@"momd"]];。
_coordinator实例变量指向NSPersistentStoreCoordinator对象。刚才我们曾经创建了托管对象模型,并令_model指向该模型,而现在我们就根据这个_model指针来初始化NSPersistentStoreCoordinator。持久化存储协调器里面目前还没有持久化存储文件,这些文件稍后将由setupCoreData方法加入。
_context实例变量指向NSManagedObjectContext对象。在初始化该对象的时候,我们把并发类型设为NSMainQueueConcurrencyType,意思是令这个上下文在主线程队列中运行。凡是要编写数据驱动型的用户界面,就需要将上下文放在主线程中。把上下文初始化好之后,我们用刚才那个指向NSPersistentStoreCoordinator的_coordinator指针来配置它。第8章将会演示如何使用多个托管对象上下文,而且还会介绍与后台运行相对应的NSPrivateQueueConcurrencyType,不过目前我们把上下文放在主线程里就可以了。
请按下列步骤修改Grocery Dude,以便添加SETUP部分:
1.将程序清单1-6中的代码添加到CoreDataHelper.m文件底部,并放在@end语句之前。
SETUP部分里还需要另外一个方法——loadStore方法,其代码如程序清单1-7所示。
程序清单1-7 CoreDataHelper.m文件中的loadStore方法
loadStore方法很简单。首先判断_store是不是已经加载好了,如果还没有,那就声明一个指向NSError实例的error指针,并将其当前值设为nil。稍后在设置_store实例变量的时候,我们会用该指针来捕获设置过程中所发生的错误。假如在执行完配置代码之后,_store是nil,那就表明配置操作失败了,我们把发生的错误及其内容记录到控制台。
通过addPersistentStoreWithType方法将SQLite持久化存储区添加到_coordinator之后,_store变量的值就是指向这个持久化存储区的指针。而调用addPersistent-StoreWithType方法时所使用的storeURL参数则是由早前创建的storeURL方法所返回的。
请按下列步骤修改Grocery Dude,以便将loadStore方法添加到SETUP部分中:
1.把程序清单1-7中的代码添加到CoreDataHelper.m文件底部,并放在@end语句之前。
最后我们来创建setupCoreData方法。由于其他的辅助方法已经就位,所以这个方法写起来非常简单。程序清单1-8列出了这个新方法的代码,目前它只需调用loadStore即可。本书稍后将为程序加入更多的功能,到那时,该方法也会随之扩充。
程序清单1-8 CoreDataHelper.m文件中的setupCoreData方法
请按下列步骤修改Grocery Dude,以便将setupCoreData方法添加到SETUP部分中:
1.把程序清单1-8中的代码添加到CoreDataHelper.m文件底部,并放在@end语句之前。
1.4.6 SAVING
接下来我们要实现的这个方法可以把_context中所发生的变更保存到_store里。具体做法很简单:只需像程序清单1-9这样,给上下文发送save:消息即可。我们将新建名为SAVING的部分,并把saveContext方法置于其中。
程序清单1-9 CoreDataHelper.m文件的SAVING部分
请按下列步骤修改Grocery Dude,以便添加SAVING部分:
1.把程序清单1-9中的代码添加到CoreDataHelper.m文件底部,并放在@end语句之前。
现在我们应该来试用一下这个CoreDataHelper了!为了使用该类,首先需要在应用程序委托的头文件(header)中添加新的属性。另外,还需引入CoreDataHelper,使头文件能够知道我们刚写好的这个类。程序清单1-10用粗体标出了应用程序委托的头文件中所需修改的代码。
程序清单1-10 AppDelegate.h
请按下列步骤修改Grocery Dude,以便将CoreDataHelper添加到应用程序委托中:
1.用程序清单1-10中的代码替换AppDelegate.h的原有内容。
下一步是修改应用程序委托的实现文件,把名为cdh的小方法放入其中,该方法会返回“非nil”的CoreDataHelper实例。另外,为了便于调试,我们还添加了一行“#define debug 1”语句,如程序清单1-11所示。
程序清单1-11 CoreDataHelper.m文件中的cdh方法
请按下列步骤修改Grocery Dude,以便将cdh方法添加到应用程序委托之中:
1.把程序清单1-11里的代码添加到AppDelegate.m文件中的“@implementation AppDelegate”这一行下面。
本小节的最后一步需要确保当应用程序转入后台或终止时,上下文里面的内容能够得以保存。这是个将数据写入磁盘的好机会——此时写入数据,不会导致用户界面反应迟缓,因为用户界面本身已经隐藏起来了。程序清单1-12列出了与上下文的保存操作有关的代码。
程序清单1-12 AppDelegate.m文件中的applicationDidEnterBackground方法
请按下列步骤修改Grocery Dude,以确保应用程序在转入后台或终止时上下文中的数据能够得以保存:
1.把[[self cdh]saveContext];这行语句添加到AppDelegate.m文件的applicationDidEnterBackground方法里。
2.把[[self cdh]saveContext];这行语句添加到AppDelegate.m文件的applicationWillTerminate方法里。
在iOS仿真器中运行Grocery Dude程序,然后按下home键(可以通过“Shift++H”组合键或iOS仿真器的Hardware>Home菜单项来模拟“按下home键”这一操作),并同时查看调试日志窗口。只有真正用到Core Data的时候,应用程序才会通过应用程序委托的cdh方法设置它,于是,在刚开始运行程序的时候,日志里面并没有内容。首次使用Core Data是在调用save:的时候,也就是应用程序转入后台的时候。本书在后续章节中还会不断地完善这个应用程序,而cdh方法的调用时机也会有所提前。图1-5演示了按下Home键之后各方法的执行顺序。
图1-5 显示在调试日志窗口中的方法的执行顺序