这是接着上一次《iOS教程:Core Data数据持久性存储基础教程》的后续教程,程序也会使用上一次制作完成的。

再上一个教程中,我们只做了一个数据模型,之后我们使用这个数据模型中的数据创建了一个表视图,我们还学习了如何测试数据模型的可行性,今天,我们来看看如何在应用启动的时候,将已经存在的数据载入或者引用到我们的程序中去。

请注意我们在上一次的教程中学习到的是直接通过操作SQLite数据库来加载数据,你当然可以一直使用这种方法,但是这个教程教授的方法更加优雅,更加合理。

在下一部分的教程中,我们将会讨论如何使用NSFetchedResultsController来优化我们的应用的访问数据的方式。

至于如果你没有上一次做好的程序的话,你可以从这里下载。

预加载/引入数据

那么我们究竟怎样把数据存储进Core Data数据库呢?目前有两种比较好的选择。

  1. 在App启动的时候从外部文件引入数据,就是在程序开始运行的时候从外部的资源,比如SQLite数据库或者XML文件中,引入数据。
  2. 提供一个已经制作完成的SQLite数据库,首先制作一个像上次的教程说的那样的数据库模型,之后在这个模型中填充数据,填充数据的方式是使用一个utility app,这个utility app可以是一个使用Core Data API填充数据库的Mac或者iOS app,也可以是一些直接填充数据库的程序。一旦数据库被填充之后,你就可以在没有已存在的数据库的情况下设置这个数据库未使用的默认数据库。

在这个教程中,我们会通过第二种,为大家展示如何使用一个简单的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” 彻底删除之。

之后找到我们上次完成的哪些文件中的

  • FailedBankCD.xcdatamodeld
  • FailedBankInfo.h
  • FailedBankInfo.m
  • FailedBankDetails.h
  • FailedBankDetails.m

将这些文件复制,或者直接拖到我们的新项目中:

确保“Copy items into destination group’s folder (if needed)” 没有选中

并且选中“Add to targets” 。

选择 main.m,你会注意到由于我们选择了使用Core Data,所以这里为我们准备了一些模板的方法,现在,我们来修改这些方法来让他们为我们的iOS程序生成数据数。

将 managedObjectModel() 方法从

  1. NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];
  2. path = [path stringByDeletingPathExtension];

替换为

  1. NSString *path = @"FailedBankCD";

这会把这个程序指向 FailedBankCD.xdatamodeld 而不是我们已经删除的CoreDataTutorial2.xdatamodeld

按下command+r进行编译和运行,应该看到没有错误。

但是如果你在这一步之前进行过编译的话,你到这时就会出现数据不符的错误,按照我们上次的教程所说的那样,删除之后重新编译运行就行。

如果你看到了下面的错误:

  1. NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator
  2. with a nil model'

这是因为程序再找一个 ‘momd’ 文件, (上一个版本的Core Data模型),但是如果你的app使用的不是这个文件的话,那就会出这个错误,最快的修正方法就是把managedObjectModel()这个方法修改为下面的:

  1. NSURL *modelURL = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"mom"]];

现在应该就没问题了

引入数据

现在到了动真家伙的时候了,真的把我们的数据加载进去。

在我们这个例子里,我们要从一个JSON文件中引入数据,也许你会想从其他类型的文件中引入数据,不过原理都是一样的。

下面,我们新建一个文件iOS – Other – Empty

把这个文件命名为Banks.json。

将下面的代码输进去:

  1. [{ "name": "Bank1", "city": "City1", "state": "State1", "zip": 11111, "closeDate": "1/1/11" },
  2. { "name": "Bank2", "city": "City2", "state": "State2", "zip": 22222, "closeDate": "2/2/12" },
  3. { "name": "Bank3", "city": "City3", "state": "State3", "zip": 33333, "closeDate": "3/3/13" },
  4. { "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文件中的数据来输进去。

现在,我们要:

  • 载入 JSON 文件
  • 解析 JSON 文件为一个 Objective C 数组
  • 枚举这个数组中的数据,为每一个物体创建一个managed item。
  • 将他们全都存入 Core Data

编程开始,首先打开 main.m 把下面的代码加入主函数:

  1. NSError* err = nil;
  2. NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
  3. NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
  4. options:kNilOptions
  5. error:&err];
  6. NSLog(@"Imported Banks: %@", Banks);

之后你的主函数看起来应该是下面这个样子的:

  1. int main(int argc, const char * argv[])
  2. {
  3.  
  4. @autoreleasepool {
  5. // Create the managed object context
  6. NSManagedObjectContext *context = managedObjectContext();
  7.  
  8. // Custom code here...
  9. // Save the managed object context
  10. NSError *error = nil;
  11. if (![context save:&error]) {
  12. NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
  13. exit(1);
  14. }
  15.  
  16. NSError* err = nil;
  17. NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
  18. NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
  19. options:kNilOptions
  20. error:&err];
  21. NSLog(@"Imported Banks: %@", Banks);
  22.  
  23. }
  24. return 0;
  25. }

这是用了使用了内置的 NSJSONSerialization API 来简单地将JSON的文件数据导入Core Foundation的数据类型中区(如NSArray,NSDictionary等等),想了解更多的话,请看 this tutorial.

运行一下这个程序,你会看到下面的输出:

  1. 2012-04-14 22:01:34.995 CoreDataTutorial2[18388:403] Imported Banks: (
  2. {
  3. city = City1;
  4. closeDate = "1/1/11";
  5. name = Bank1;
  6. state = State1;
  7. zip = 11111;
  8. },
  9. {
  10. city = City2;
  11. closeDate = "2/2/12";
  12. name = Bank2;
  13. state = State2;
  14. zip = 22222;
  15. },
  16. {
  17. city = City3;
  18. closeDate = "3/3/13";
  19. name = Bank3;
  20. state = State3;
  21. zip = 33333;
  22. },
  23. {
  24. city = City4;
  25. closeDate = "4/4/14";
  26. name = Bank4;
  27. state = State4;
  28. zip = 44444;
  29. }
  30. )

现在我们已经能够把这些数据存储进了一个Objective – C的物体中,那么现在我们就可以像上次教程的末尾那样把这些数据输入进Core Data的数据库中。

首先在头部加上一下你需要的文件的引用语句:

  1. #import "FailedBankInfo.h"
  2. #import "FailedBankDetails.h"

之后把这些你之前加入主函数代码。

  1. [Banks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  2. FailedBankInfo *failedBankInfo = [NSEntityDescription
  3. insertNewObjectForEntityForName:@"FailedBankInfo"
  4. inManagedObjectContext:context];
  5. failedBankInfo.name = [obj objectForKey:@"name"];
  6. failedBankInfo.city = [obj objectForKey:@"city"];
  7. failedBankInfo.state = [obj objectForKey:@"state"];
  8. FailedBankDetails *failedBankDetails = [NSEntityDescription
  9. insertNewObjectForEntityForName:@"FailedBankDetails"
  10. inManagedObjectContext:context];
  11. failedBankDetails.closeDate = [NSDate dateWithString:[obj objectForKey:@"closeDate"]];
  12. failedBankDetails.updateDate = [NSDate date];
  13. failedBankDetails.zip = [obj objectForKey:@"zip"];
  14. failedBankDetails.info = failedBankInfo;
  15. failedBankInfo.details = failedBankDetails;
  16. NSError *error;
  17. if (![context save:&error]) {
  18. NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
  19. }
  20. }];
  21.  
  22. // Test listing all FailedBankInfos from the store
  23. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
  24. NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo"
  25. inManagedObjectContext:context];
  26. [fetchRequest setEntity:entity];
  27. NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
  28. for (FailedBankInfo *info in fetchedObjects) {
  29. NSLog(@"Name: %@", info.name);
  30. FailedBankDetails *details = info.details;
  31. NSLog(@"Zip: %@", details.zip);
  32. }

这些代码本质上就是我们上一次使用的代码,除了我们这次使用了enumerateObjectsUsingBlock: 的方法老枚举这个数组的内容之后进行插入,之后我们使用一个Fetch命令来输出数据。

现在运行一下,你会看到输出了之前的数组。

  1. 2012-04-14 22:15:44.149 CoreDataTutorial2[18484:403] Name: Bank1
  2. 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Zip: 11111
  3. 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Name: Bank2
  4. 2012-04-14 22:15:44.151 CoreDataTutorial2[18484:403] Zip: 22222
  5. 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Name: Bank3
  6. 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Zip: 33333
  7. 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Name: Bank4
  8. 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Zip: 44444

Ok,这些就是你在Core Data中的数据了。除了这种简单的JSON文件之外,你也可以使用更加复杂的JSON文件,XML文件,甚至是普通的表格文件,只要你存成了csv的格式,也可以是来自互联网的pipe,可以使用的文件种类数也数不清,我们以后也会详细介绍。

Xcode犯病了?

下面我们做一个脑部移植手术,将我们使用Mac OS X上的命令行程序的数据库转移到iPhone app中去。最简单的找到数据库文件的方法就是右键(ctrl+) CoreDataTutorial2 产品揽之后按 “Show in Finder”。

这会打开一个新的Finder窗口,在这里面会有这些文件:

  • Banks.json – 这是数据的原始文件,记得吗?
  • CoreDataTutorial2 – 这个是应用本身。
  • FailedBankCD.momd (或者 .mom) – 这是编译好的Core Data数据模型。
  • CoreDataTutorial2.sqlite – 这就是我们在找的sqlite数据库文件,它是由程序生成的,Core Data应该可以通用的。你可以自己找一个SQLite数据库的查看软件,也可以下载 这个

确定 “CoreDataTutorial2.sqlite” 就是我么所需要的文件,下面我们把这个文件拷贝到我们上一个教程的源码工程文件之中,之后打开:

从Finder中拖拽 “CoreDataTutorial2.sqlite” 文件到Xcode的工程之中,确保 “Copy items into destination group’s folder (if needed)” 这个选项没有被选中,另一个是选中的。

最后,打开 “FBCDAppDelegate.m”,找到 persistentStoreCoordinator 方法,在 NSURL *storeURL = [[self app... 这一行的下面加入以下的代码:

  1. if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
  2. NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"CoreDataTutorial2" ofType:@"sqlite"]];
  3. NSError* err = nil;
  4.  
  5. if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
  6. NSLog(@"Oops, could copy preloaded data");
  7. }
  8. }

这一段的代码是为了检测sqlite数据库是否已经存在与这个app之中,如果不存在,就会找到我们预加载的数据库,之后把这个数据库复制到正常的路径,超级简单,来,让我们试试!

看到了原本在JSON文件中的四个Banks,之后还有一个我们在第一个教程中加入的Test Bank,如果你没有看到的话,八成是数据库已经存在了,山茶模拟器中的App之后重新运行。

之后看些什么?

这是我制作完成的例子程序源码,欢迎下载。

这是原作者的样板程序:here (direct download)

欢迎关注我的围脖: @Oratis

在知乎和豆瓣上,我的名字也是Oratis

我会把之后发表的教程分享到这些社交网络中。

如果你有任何问题,欢迎在底下留言,也欢迎写信给我,我的邮箱地址是: oratis@appkon.com

iOS教程:如何使用Core Data – 预加载和引入数据的更多相关文章

  1. Laravel使用whereHas进行过滤不符合条件的预加载with数据

    问题描述:目前有用户表,文章表,文章评论表,收藏表.我需要获我的收藏文章列表(可以被搜索,通过分类,文章标题等),通过收藏预加载with文章表,文章评论表,文章用户表 解决办法:通过whereHas限 ...

  2. 防止ViewPager和Fragment结合使用时候的数据预加载

    不知道你们使用ViewPager和Fragment结合的时候发现一个问题没,如果你的每个Fragment都需要请求网络数据,并且你在请求网络数据的时候会加入进度对话框的加载显示效果,当你显示第一个Fr ...

  3. laravel 关联中的预加载

    预加载 当作为属性访问 Eloquent 关联时,关联数据是「懒加载」的.意味着在你第一次访问该属性时,才会加载关联数据.不过,是当你查询父模型时,Eloquent 可以「预加载」关联数据.预加载避免 ...

  4. 预加载与智能预加载(iOS)

    来源:Draveness(@Draveness) 链接:http://www.jianshu.com/p/1519a5302141 前两次的分享分别介绍了 ASDK 对于渲染的优化以及 ASDK 中使 ...

  5. iOS开发过程中使用Core Data应避免的十个错误

    原文出处: informit   译文出处:cocoachina Core Data是苹果针对Mac和iOS平台开发的一个框架,主要用来储存数据.对很多开发者来说,Core Data比较容易入手,但很 ...

  6. HBuilder mui 手机app开发 Android手机app开发 ios手机app开发 打开新页面 预加载页面 关闭页面

    创建子页面 在mobile app开发过程中,经常遇到卡头卡尾的页面,此时若使用局部滚动,在android手机上会出现滚动不流畅的问题: mui的解决思路是:将需要滚动的区域通过单独的webview实 ...

  7. Angular - 预加载 Angular 模块

    Angular - 预加载延迟模块 在使用路由延迟加载中,我们介绍了如何使用模块来拆分应用,在访问到这个模块的时候, Angular 加载这个模块.但这需要一点时间.在用户第一次点击的时候,会有一点延 ...

  8. spine实现预加载(一)

    前言 本文实现了spine动画的预加载,解决在战斗等大量加载spine动画的时候出现卡顿现象. 这里使用和修改三个类,直接修改的源码,当然你也可以继承LuaSkeletonAnimation,自己封装 ...

  9. 使用 SVG 实现一个漂亮的页面预加载效果

    今天我们要为您展示如何使用 CSS 动画, SVG 和 JavaScript 创建一个简单的页面预加载效果.对于网站来说,这些预载入得画面提供了一种创造性的方法,使用户在等待内容加载的时候不会那么无聊 ...

随机推荐

  1. 通过nbviwer在线分享python notebook

    在数据科学计算中,jupyter-notebook是一个很得力的助手,但是Notebook写完之后如何与他人分享呢?我们可以使用nbviwer. 具体思路: 具体的方法如下: 本地编写ipython ...

  2. BZOJ3597 [Scoi2014]方伯伯运椰子 【二分 + 判负环】

    题目链接 BZOJ3597 题解 orz一眼过去一点思路都没有 既然是流量网络,就要借鉴网络流的思想了 我们先处理一下那个比值,显然是一个分数规划,我们二分一个\(\lambda = \frac{X ...

  3. php56升级后php7 mcrypt_encrypt 报错

    mcrypt_encrypt(MCRYPT_BLOWFISH, $passphrase, $data, MCRYPT_MODE_CBC, $iv); openssl_encrypt($data, &q ...

  4. MATLAB矩阵操作大全

    转载自:http://blog.csdn.net/dengjianqiang2011/article/details/8753807 MATLAB矩阵操作大全 一.矩阵的表示 在MATLAB中创建矩阵 ...

  5. 《c程序设计语言》-2.6~2.8

    #include <stdio.h> unsigned setbits(unsigned x, int p, int n, unsigned y) { return (x & (( ...

  6. pip3 快速安装

    https://www.cnblogs.com/wenchengxiaopenyou/p/5709218.html

  7. Android Handler使用

    1. 介绍 Handler允许向关联线程的消息队列(MessageQueue)发送消息(Message)和可执行对象(Runnable).每个Handler实例都与某个线程(即创建该Handler的线 ...

  8. (10)ubuntu内核源码树

    ubuntu内核源码树目录: root@ubuntu:/lib/modules/3.13.0-32-generic/build#

  9. unicode和utf8之间的关系

    ,字符编码是计算机技术的基石,想要熟练使用计算机,懂得一点字符编码的知识,还是很有必要的. 1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit ...

  10. c专家编程读书笔记

    无论在什么时候,如果遇到malloc(strlen(str));,几乎可以直接断定他是错误的,而malloc(strlen(str)+1):才是正确的: 一个L的NUL哟关于结束一个ACSII字符串: ...