这是接着上一次《iOS教程:Core Data数据持久性存储基础教程》的后续教程,程序也会使用上一次制作完成的。
再上一个教程中,我们只做了一个数据模型,之后我们使用这个数据模型中的数据创建了一个表视图,我们还学习了如何测试数据模型的可行性,今天,我们来看看如何在应用启动的时候,将已经存在的数据载入或者引用到我们的程序中去。
请注意我们在上一次的教程中学习到的是直接通过操作SQLite数据库来加载数据,你当然可以一直使用这种方法,但是这个教程教授的方法更加优雅,更加合理。
在下一部分的教程中,我们将会讨论如何使用NSFetchedResultsController来优化我们的应用的访问数据的方式。
至于如果你没有上一次做好的程序的话,你可以从这里下载。
那么我们究竟怎样把数据存储进Core Data数据库呢?目前有两种比较好的选择。
在这个教程中,我们会通过第二种,为大家展示如何使用一个简单的utility app来预加载一个已经装在好的Core Data数据库,以便让你的app使用。
我们在iOS上使用Core Data的方法的基础和我们在Mac OS X上使用的是一致的,他们使用同样的模型和类。
这一为我们可以写一个MAC OS X上的简单的console程序,来从数据源引入数据,再把这个数据库的数据库拿来给我们的iOS程序来用,不错吧?
我们来试试,首先打开Xcode,在 Mac OSX类中的Application中使用Command Line Tool 的模板。
我们就用 “CoreDataTutorial2” 作为工程的名字吧,记得使用“Core Data” 和 “Use Automatic Reference Counting” 。
完成创建之后,选择 “CoreDataTutorial2.xcdatamodeld” 彻底删除之。
之后找到我们上次完成的哪些文件中的
将这些文件复制,或者直接拖到我们的新项目中:
确保“Copy items into destination group’s folder (if needed)” 没有选中
并且选中“Add to targets” 。
选择 main.m,你会注意到由于我们选择了使用Core Data,所以这里为我们准备了一些模板的方法,现在,我们来修改这些方法来让他们为我们的iOS程序生成数据数。
将 managedObjectModel()
方法从
NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]; path = [path stringByDeletingPathExtension]; |
替换为
NSString *path = @"FailedBankCD"; |
这会把这个程序指向 FailedBankCD.xdatamodeld
而不是我们已经删除的CoreDataTutorial2.xdatamodeld
。
按下command+r进行编译和运行,应该看到没有错误。
但是如果你在这一步之前进行过编译的话,你到这时就会出现数据不符的错误,按照我们上次的教程所说的那样,删除之后重新编译运行就行。
如果你看到了下面的错误:
NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator with a nil model'
这是因为程序再找一个 ‘momd’ 文件, (上一个版本的Core Data模型),但是如果你的app使用的不是这个文件的话,那就会出这个错误,最快的修正方法就是把managedObjectModel()这个方法修改为下面的:
现在应该就没问题了
现在到了动真家伙的时候了,真的把我们的数据加载进去。
在我们这个例子里,我们要从一个JSON文件中引入数据,也许你会想从其他类型的文件中引入数据,不过原理都是一样的。
下面,我们新建一个文件iOS – Other – Empty
把这个文件命名为Banks.json。
将下面的代码输进去:
[{ "name": "Bank1", "city": "City1", "state": "State1", "zip": 11111, "closeDate": "1/1/11" }, { "name": "Bank2", "city": "City2", "state": "State2", "zip": 22222, "closeDate": "2/2/12" }, { "name": "Bank3", "city": "City3", "state": "State3", "zip": 33333, "closeDate": "3/3/13" }, { "name": "Bank4", "city": "City4", "state": "State4", "zip": 44444, "closeDate": "4/4/14" } ] |
这是一个一个数组中包含四个字典的JSON文件,每一个字典都有几个与FailedBankInfo/FailedBankDetails中的物体相对应的属性。
如果你不是很清楚JSON文件是如何组织数据的,你可以看一下这个教程: this tutorial.
接下来,我们告诉我们的应用当编译的时候将这个文件我们的产品目录,看图做,首先选择Project,之后选择CoreDataTutorial2目标,选择Build Phase选项卡,按下“Add Build Phase”,选择“Add Copy File”,选择目标位置为“Products Directory”,最后,把“Banks。json拖到Add Files的部分。
当一个应用启动时,要先使用FailedBank的数据模型和类初始化一个Core Data的数据库,之后用Banks.json文件中的数据来输进去。
现在,我们要:
编程开始,首先打开 main.m 把下面的代码加入主函数:
NSError* err = nil; NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"]; NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath] options:kNilOptions error:&err]; NSLog(@"Imported Banks: %@", Banks); |
之后你的主函数看起来应该是下面这个样子的:
int main(int argc, const char * argv[]) { @autoreleasepool { // Create the managed object context NSManagedObjectContext *context = managedObjectContext(); // Custom code here... // Save the managed object context NSError *error = nil; if (![context save:&error]) { NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error"); exit(1); } NSError* err = nil; NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"]; NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath] options:kNilOptions error:&err]; NSLog(@"Imported Banks: %@", Banks); } return 0; } |
这是用了使用了内置的 NSJSONSerialization API 来简单地将JSON的文件数据导入Core Foundation的数据类型中区(如NSArray,NSDictionary等等),想了解更多的话,请看 this tutorial.
运行一下这个程序,你会看到下面的输出:
2012-04-14 22:01:34.995 CoreDataTutorial2[18388:403] Imported Banks: ( { city = City1; closeDate = "1/1/11"; name = Bank1; state = State1; zip = 11111; }, { city = City2; closeDate = "2/2/12"; name = Bank2; state = State2; zip = 22222; }, { city = City3; closeDate = "3/3/13"; name = Bank3; state = State3; zip = 33333; }, { city = City4; closeDate = "4/4/14"; name = Bank4; state = State4; zip = 44444; } ) |
现在我们已经能够把这些数据存储进了一个Objective – C的物体中,那么现在我们就可以像上次教程的末尾那样把这些数据输入进Core Data的数据库中。
首先在头部加上一下你需要的文件的引用语句:
#import "FailedBankInfo.h" #import "FailedBankDetails.h" |
之后把这些你之前加入主函数代码。
[Banks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { FailedBankInfo *failedBankInfo = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankInfo" inManagedObjectContext:context]; failedBankInfo.name = [obj objectForKey:@"name"]; failedBankInfo.city = [obj objectForKey:@"city"]; failedBankInfo.state = [obj objectForKey:@"state"]; FailedBankDetails *failedBankDetails = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankDetails" inManagedObjectContext:context]; failedBankDetails.closeDate = [NSDate dateWithString:[obj objectForKey:@"closeDate"]]; failedBankDetails.updateDate = [NSDate date]; failedBankDetails.zip = [obj objectForKey:@"zip"]; failedBankDetails.info = failedBankInfo; failedBankInfo.details = failedBankDetails; NSError *error; if (![context save:&error]) { NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]); } }]; // Test listing all FailedBankInfos from the store NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo" inManagedObjectContext:context]; [fetchRequest setEntity:entity]; NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error]; for (FailedBankInfo *info in fetchedObjects) { NSLog(@"Name: %@", info.name); FailedBankDetails *details = info.details; NSLog(@"Zip: %@", details.zip); } |
这些代码本质上就是我们上一次使用的代码,除了我们这次使用了enumerateObjectsUsingBlock: 的方法老枚举这个数组的内容之后进行插入,之后我们使用一个Fetch命令来输出数据。
现在运行一下,你会看到输出了之前的数组。
2012-04-14 22:15:44.149 CoreDataTutorial2[18484:403] Name: Bank1 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Zip: 11111 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Name: Bank2 2012-04-14 22:15:44.151 CoreDataTutorial2[18484:403] Zip: 22222 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Name: Bank3 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Zip: 33333 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Name: Bank4 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Zip: 44444 |
Ok,这些就是你在Core Data中的数据了。除了这种简单的JSON文件之外,你也可以使用更加复杂的JSON文件,XML文件,甚至是普通的表格文件,只要你存成了csv的格式,也可以是来自互联网的pipe,可以使用的文件种类数也数不清,我们以后也会详细介绍。
下面我们做一个脑部移植手术,将我们使用Mac OS X上的命令行程序的数据库转移到iPhone app中去。最简单的找到数据库文件的方法就是右键(ctrl+) CoreDataTutorial2
产品揽之后按 “Show in Finder”。
这会打开一个新的Finder窗口,在这里面会有这些文件:
确定 “CoreDataTutorial2.sqlite” 就是我么所需要的文件,下面我们把这个文件拷贝到我们上一个教程的源码工程文件之中,之后打开:
从Finder中拖拽 “CoreDataTutorial2.sqlite” 文件到Xcode的工程之中,确保 “Copy items into destination group’s folder (if needed)” 这个选项没有被选中,另一个是选中的。
最后,打开 “FBCDAppDelegate.m”,找到 persistentStoreCoordinator
方法,在 NSURL *storeURL = [[self app...
这一行的下面加入以下的代码:
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) { NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"CoreDataTutorial2" ofType:@"sqlite"]]; NSError* err = nil; if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) { NSLog(@"Oops, could copy preloaded data"); } } |
这一段的代码是为了检测sqlite数据库是否已经存在与这个app之中,如果不存在,就会找到我们预加载的数据库,之后把这个数据库复制到正常的路径,超级简单,来,让我们试试!
看到了原本在JSON文件中的四个Banks,之后还有一个我们在第一个教程中加入的Test Bank,如果你没有看到的话,八成是数据库已经存在了,山茶模拟器中的App之后重新运行。
这是我制作完成的例子程序源码,欢迎下载。
这是原作者的样板程序:here (direct download)
欢迎关注我的围脖: @Oratis
在知乎和豆瓣上,我的名字也是Oratis
我会把之后发表的教程分享到这些社交网络中。
如果你有任何问题,欢迎在底下留言,也欢迎写信给我,我的邮箱地址是: [email protected]