这是接着上一次《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() 方法从

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()这个方法修改为下面的:

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

现在应该就没问题了

引入数据

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

在我们这个例子里,我们要从一个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文件中的数据来输进去。

现在,我们要:

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

编程开始,首先打开 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,可以使用的文件种类数也数不清,我们以后也会详细介绍。

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... 这一行的下面加入以下的代码:

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

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

如果你有任何问题,欢迎在底下留言,也欢迎写信给我,我的邮箱地址是: 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. [译]如何去除pandas dataframe里面的Unnamed的列?

    原文来源: https://stackoverflow.com/questions/43983622/remove-unnamed-columns-in-pandas-dataframe 问:我有一个 ...

  2. PAT 甲级 1011 World Cup Betting

    https://pintia.cn/problem-sets/994805342720868352/problems/994805504927186944 With the 2010 FIFA Wor ...

  3. Linux运维文档之nginx

    NGINX安装配置1.检查并且安装依赖组件检查安装nginx的依赖性,nginx的模块需要第三方库的支持,检查是否安装下列库:zlib.zlib-devel.openssl.openssl-devel ...

  4. BZOJ1196 [HNOI2006]公路修建问题 【二分 + Kruskal】

    题目 OI island是一个非常漂亮的岛屿,自开发以来,到这儿来旅游的人很多.然而,由于该岛屿刚刚开发不久,所以那里的交通情况还是很糟糕.所以,OIER Association组织成立了,旨在建立O ...

  5. 异或值 xor

    题目描述 给出一个 N 个点的带权无向图,要求从 1 号点到 N 号点的一条路径,使得路径上的边 权异或值最大. 输入格式 第一行包含两个整数N和 M, 表示该无向图中点的数目与边的数目. 接下来M ...

  6. Altium Designer 网络连接方式Port和Net Label详解

    1.图纸结构      图纸包括两种结构关系: 一种是层次式图纸,该连接关系是纵向的,也就是某一层次的图纸只能和相邻的上级或下级有关系:另一种是扁平式图纸,该连接关系是横向的,任何两张图纸之间都可以建 ...

  7. 通过hover修改其他元素

    hover,我们都知道,是监听组件“悬停状态”的一个伪类. 我们一般通过hover来修改组件的背景什么的,很少涉及到太复杂的操作.也就是说我们一般只是对加了hover伪类的元素自身的样式进行改变,比如 ...

  8. flask框架基本使用(2)(响应与重定向)

    #转载请留言联系 flask 框架基本使用(1):https://www.cnblogs.com/chichung/p/9756935.html 1. flask 自定义返回状态码与响应头 from ...

  9. python里如何计算大文件的md5

    在python3中,有了一个hashlib,可以用来计算md5,这里先给出一个简单的例子: import hashlib sstr="i love hanyu" print(has ...

  10. [BZOJ2152]聪聪可可 点分治/树形dp

    2152: 聪聪可可 Time Limit: 3 Sec  Memory Limit: 259 MB Submit: 3602  Solved: 1858 [Submit][Status][Discu ...