本节书摘来自华章出版社《core data应用开发实践指南》一书中的第1章,第1.4节,作者 (美)tim roadley,更多章节内容可以访问云栖社区“华章计算机”公众号查看
在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-2所示,选中grocery dude target。
在general分页中,点击linked frameworks and libraries区域中的“+”按钮,然后把项目链接到coredata.framework,如图1-2所示。
试着查看一下支持core data的那些内置模板,你也许会发现:core data是在应用程序委托(application delegate)里面设置的。本书所用的办法是通过辅助类来设置core data,这样的话,你就可以将这种办法运用到自己的项目中了。这也使得core data组件变得模块化,而且易于移植。我们将通过应用程序委托来惰性地(lazily)创建coredatahelper类的实例。这种实例可用来完成下列事项:
初始化托管对象模型
根据托管对象模型来创建持久化存储区,并据此初始化持久化存储协调器
根据持久化存储协调器来初始化托管对象上下文
请根据下列步骤修改grocery dude,以便在新的xcode 组(group)里面创建coredatahelper类:
右击xcode中的grocery dude组,然后创建名为generic core data classes的新组,如图1-3所示。
选定刚创建好的generic core data classes 组。
点击file > new > file...。
新建ios > cocoa touch > objective-c class,然后点击next按钮。
将subclass of设为nsobject,并将class的名称改为coredatahelper,然后点击next按钮。
在targets中勾选“grocery dude”,然后点击create按钮,这样就会在grocery dude项目的目录中创建coredatahelper类了。
程序清单1-1列出了需要添加到coredatahelper头文件中的新代码。
作为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中的代码把coredatahelper.h文件里现有的全部代码都替换掉。假如现在查看coredatahelper.m文件,那么xcode会提示你setupcoredata及savecontext方法还没实现,不过目前这无关紧要。
这个辅助类一开始会有四个主要的部分,它们分别叫做files、paths、setup及saving。为了便于查看及阅读代码,笔者用编译指示标记(pragma mark)将这些区域分隔开。编译指示标记使得程序员可以按照逻辑来组织源码,而且xcode还会据此生成一份漂亮的菜单,以供用户在不同的部分中浏览。
coredatahelper.m的files 部分中会有一个nsstring,用于存储持久化存储区的文件名。如果稍后还要添加其他持久化存储区,那么也应该在这里写下它们的文件名。程序清单1-2中的代码包含一条#define语句,grocery dude中的绝大多数类都要使用这行语句,它是为了协助调试工作而设的。如果debug是1,那么该类的debug logging(调试日志记录)功能就会开启。本书大部分的nslog命令都包裹在if(debug==1)语句里,所以只有开启了调试功能之后,那些命令才会有效果。
请按下述步骤修改grocery dude,以便添加files部分:
把程序清单1-2中的代码添加到coredatahelper.m文件底部,但是要将其置于@end语句之前。
为了把数据以持久化的形式写入磁盘,core data需要知道持久化存储文件在文件系统中的位置。我们可以分别编写3个方法来向core data提供这一信息。程序清单1-3列出了第一个方法,也就是applicationdocumentsdirectory方法,该方法返回nsstring,而这个nsstring就代表应用程序文档目录的路径。你会注意到我们这是首次把调试代码包裹在if(debug==1)语句里面,这种调试代码用于显示当前运行的究竟是哪个方法。nslog语句很适合用来打印应用程序里各个方法的执行顺序,而这对调试工作很有帮助。
请按下列步骤修改grocery dude,以便添加paths部分:
将程序清单1-3中的代码添加到coredatahelper.m文件尾部,并放在@end语句之前。
接下来这个方法叫做applicationstoresdirectory,它会向应用程序文档目录中添加名为stores的子目录,并且将其路径放在nsurl中返回。若stores目录尚未建立,则程序清单1-4中的相关代码会将其创建出来。
请按下列步骤修改grocery dude,以便将applicationstoresdirectory方法添加到paths 部分中:
将程序清单1-4中的代码添加到coredatahelper.m文件尾部,并放在@end语句之前。
最后一个方法如程序清单1-5所示,它只是把持久化存储区的文件名添加到stores目录的路径中。调用完该方法之后,我们就可以知道持久化存储文件的完整路径了。
请按下列步骤修改grocery dude,以便将storeurl方法添加到paths部分中:
将程序清单1-5中的代码添加到coredatahelper.m文件尾部,并放在@end语句之前。
处理完文件和路径之后,现在该实现初始化core data所用的三个方法了。程序清单1-6列出了第一个方法,它的名字叫做init,程序在创建coredatahelper实例的时候,会自动运行该方法。
_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-6中的代码添加到coredatahelper.m文件底部,并放在@end语句之前。
setup 部分里还需要另外一个方法—loadstore方法,其代码如程序清单1-7所示。
loadstore方法很简单。首先判断_store是不是已经加载好了,如果还没有,那就声明一个指向nserror实例的error指针,并将其当前值设为nil。稍后在设置_store实例变量的时候,我们会用该指针来捕获设置过程中所发生的错误。假如在执行完配置代码之后,_store是nil,那就表明配置操作失败了,我们把发生的错误及其内容记录到控制台。
通过addpersistentstorewithtype方法将sqlite持久化存储区添加到_coordinator之后,_store变量的值就是指向这个持久化存储区的指针。而调用addpersistent-storewithtype方法时所使用的storeurl参数则是由早前创建的storeurl方法所返回的。
请按下列步骤修改grocery dude,以便将loadstore方法添加到setup 部分中:
把程序清单1-7中的代码添加到coredatahelper.m文件底部,并放在@end语句之前。
最后我们来创建setupcoredata方法。由于其他的辅助方法已经就位,所以这个方法写起来非常简单。程序清单1-8列出了这个新方法的代码,目前它只需调用loadstore即可。本书稍后将为程序加入更多的功能,到那时,该方法也会随之扩充。
请按下列步骤修改grocery dude,以便将setupcoredata方法添加到setup部分中:
把程序清单1-8中的代码添加到coredatahelper.m文件底部,并放在@end语句之前。
接下来我们要实现的这个方法可以把_context中所发生的变更保存到_store里。具体做法很简单:只需像程序清单1-9这样,给上下文发送save:消息即可。我们将新建名为saving的部分,并把savecontext方法置于其中。
请按下列步骤修改grocery dude,以便添加saving部分:
把程序清单1-9中的代码添加到coredatahelper.m文件底部,并放在@end语句之前。
现在我们应该来试用一下这个coredatahelper了!为了使用该类,首先需要在应用程序委托的头文件(header)中添加新的属性。另外,还需引入coredatahelper,使头文件能够知道我们刚写好的这个类。程序清单1-10用粗体标出了应用程序委托的头文件中所需修改的代码。
请按下列步骤修改grocery dude,以便将coredatahelper添加到应用程序委托中:
用程序清单1-10中的代码替换appdelegate.h的原有内容。
下一步是修改应用程序委托的实现文件,把名为cdh的小方法放入其中,该方法会返回“非nil”的coredatahelper实例。另外,为了便于调试,我们还添加了一行“#define debug 1”语句,如程序清单1-11所示。
请按下列步骤修改grocery dude,以便将cdh方法添加到应用程序委托之中:
把程序清单1-11里的代码添加到appdelegate.m文件中的“@implementation appdelegate”这一行下面。
本小节的最后一步需要确保当应用程序转入后台或终止时,上下文里面的内容能够得以保存。这是个将数据写入磁盘的好机会—此时写入数据,不会导致用户界面反应迟缓,因为用户界面本身已经隐藏起来了。程序清单1-12列出了与上下文的保存操作有关的代码。
请按下列步骤修改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键之后各方法的执行顺序。