在iPhone所有数据存储的方法里面,Core Data是重要数据存储的最佳选择。它能降低你应用的内存开销,提升响应速度,并把你从繁琐的代码中解脱出来。
然而,学习Core Data之路异常久远。不过这也是这一系列教程的由来 – 让你快速掌握Core Data基础知识。
作为该系列教程的第一部分,我们将为我们的对象建立一个可视化数据模型。为保证其有效性,我们会做一个快速肮脏测试(dirty test验证其健壮性和有效性),然后将其勾在一个表视图(table view)里,这样我们可以看到这一列对象。
系列教程的第二部分,我们将讨论如何将数据预先载入到Core Data中,这样我们的应用启动时就初始化好了。
最后的部分,我们将讨论如何使用NSFetchedResultsController来优化应用,达到降低内存开销以及改进响应时间的目的。
在本教程开始之前,我建议先翻阅我以前写的SQLite for iPhone Developers教程,这会让你更容易理解。另外,2个教程做的应用(app)都是一样的,只是这次我们用的是Core Data!
创建一个Core Data工程
下面让我们开始!建立一个新的Window-based Application,勾选“Use Core Data for storage”,将工程命名为“FailedBanksCD.”
在开始前,我们快速看一下建好的工程。首先展开Resources并双击FailedBanksCD.xcdatamodel,会弹出一个可视化编辑器-这就是我们接下来会用到的模型对象图示。现在我们先把它关闭。
然后看看FailedBanksCDAppDelegate.m。在这里你会看见已经为我们实现的一些新函数,用于建立Core Data”堆”。新增了一个 managed object context,一个managed object model, persistent store coordinator. 啊??
别担心。名字虽然开始听起来奇怪,一旦你打通“任督二脉”就很容易理解到他们是什么。
- Managed Object Model:你可以把它当作是数据库规划图。这是一个包含其他对象(也叫Entities)定义的类,对象存储在数据库里面。通常会用刚刚瞄过的可视化编辑器来设定数据库的对象,它们的特性(attributes),它们是如何相互关联的。然而,你也可以用代码来实现。
- Persistent Store Coordinator:你可以把它当作是数据库的连接。在这里你设定用来存储对象的数据库的真实名称和位置,以及需要时用来保存的管理对象上下文(managed object context)。
- Managed Object Context:你可以把它当作是一个数据库取出对象的“暂存器”。对我们来说它也是这三个里面最重要的,因为我们大多都在上面工作。基本上,无论何时需要获取对象,插入对象或是删除对象,你都要调用管理对象上下文(managed object context)的方法(或者说大部分时间!)。
这是系列教程的第二部分,有助于你加快掌握基本的Core Data内容。
在系列教程一中,我们为对象建立了可视化数据模型,运行了快速肮脏测试并勾在一个表视图(table view)中来显示。而在这个教程,我们将讨论如何把已有的数据导入或者预先载入到Core Data里面,这样我们的应用开始时会有一些好的默认数据。
在系列教程的最后部分,我们将讨论如何使用NSFetchedResultsController来优化应用,降低内存开销和提升响应时间。
Preloading / Importing Existing Data
预载/导入已有数据
我们要如何在Core Data中预先载入数据呢?流行的解决方案有两种:
- 启动时从外部源填入Core Data。对此,注意到数据库还没有导入,应用可以启动时从外部来源(例如SQLite数据库或是XML文件)读取数据。
- 在SQLite数据库中预先填充。对此我们可以Core Data在模型基础上建立数据库结构,然后使用工具来填入数据库。这类工具可以是基于Core Data API的Mac或者iPhone应用,或是一些直接填入SQLite数据库的程序。一旦数据库填好了,只需将其包含到应用里面作为默认数据库,在不存在已有的数据库情况下。
我们将会采用第二种方式,因为它更简单更有效。为了填充数据库,我们只需稍微扩展一下已有的Python脚本。
注意到为什么用Python脚本而不是用基于Core Data API的工具来来导入数据,是因为我们现在有点走底层的样子,将来可能会容易损坏…但是对于本教程我认为第一,由于我们刚刚接触了SQLite,这样对学习的经验巩固更好,而且对事情的进展看的更加清楚;第二,更简单!
所以,让我们拿出工程中产生的sqlite数据库的拷贝。找出相关文件的最简单的方法就是在程序委托(application delegate)中persistentStoreCoordinator函数里面storeUrl一行下方设置一个断点。你可以通过检测storeUrl变量的内容来获取sqlite数据库备份文件的完整路径。找到它并拷到你的Python脚本目录。
一旦你找到数据库,使用sqlite3来简单看看数据库的模样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
这里有段快速描述。Z_METADATA包含了一些关于Core Data需要实现的模型信息。Z_PRIMARYKEY包含了(在其他东西里面)各个实体所用到的最大key。
至于ZFAILEDBANKINFO跟ZFAILEDBANKDETAILS,这些是我们的主要数据表。Z_PK是各个表的唯一id,Z_ENT是他们的实体id(跟Z_PRIMARYKEY表中列出的一样),最后那些是我们的普通字段。
迄今为止,我们现在的处境跟当初用SQLite3的时候一样。然而,我们不需要写如此多的代码(注意一下FailedBankDatabase类中那段缺失的原始SQL语句代码),添加诸如插入/删除之类的操作也很简便。
有一项显著的便利性是用Core Data才能体现出来的:使用NSFetchedResultsController。
比较理想的状态是在用户目前所见的table view中加载部分行。幸运的是,苹果为我们造就了这一便利,提供了一个很棒的工具类:NSFetchedResultsController。
因此,开始打开FailedBanksListViewController.h,移除旧有的NSArray failedBankInfos,替换为一个新的NSFetchedResultsController:
@interface FailedBanksListViewController : UITableViewController <NSFetchedResultsControllerDelegate> { NSFetchedResultsController *_fetchedResultsController; NSManagedObjectContext *_context;}@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;@property (nonatomic, retain) NSManagedObjectContext *context;@end |
在synthesize段移除老的failedBankInfos代码并添加:
@synthesize fetchedResultsController = _fetchedResultsController; |
以防忘记,在dealloc方法中fetchedResultsController设为nil
self.fetchedResultsController = nil; |
另一个绝妙的地方就是你可以在viewDidUnload里面将NSFetchedResultsController设为nil,这就意味着在低内存情况下,内存的所有数据都可以释放掉(在视图离开视线的情况下)。你只需要在viewDidUnload里面把它清空(并保证在viewDidLaod中重新初始化)
- (void)viewDidUnload { self.fetchedResultsController = nil;} |