CoreData入门


    对于所有的持久化储存数据的方式,Core Data是iOS上最好的储存重要数据的方法。它可以减少你的app的内存占用,提高响应速度,并且大大减少你编写的重复而僵化的代码。

    但是Core Data的学习曲线可能会非常长。这就是这个学习系列存在的原因。就是要提高你学习Core Data的速度。

创建一个Core Data工程

让我们开始吧!打开Xcode并且使用Master-Detail模板创建一个工程。


在下一屏中,输入工程名FailedBackCD,



在开始之前,我们先去掉这些程序自动为我们创建的模板代码。选择如下文件并且从工程中去掉:

  • FBCDMasterViewController.h
  • FBCDMasterViewController.m
  • FBCDDetailViewController.h
  • FBCDDetailViewController.m

在删除时,对话框询问删除引用还是移到废纸篓时选择移除到废纸篓。

右键单击FailedBankCD组文件夹选择new File.选择iOS\Objective-C class模板然后选择Next.创建FBCDMasterViewController并且让它成为UITableViewController的子类。保证两个选择框都没有被选中。点击Next进行创建。

选择FBCDMasterViewController.h 并且在@end之前加入如下代码:

@property (nonatomic,strong) NSManagedObjectContext* managedObjectContext;

现在去.m文件,并且在@implement之后对这个属性进行同步。

@synthesize managedObjectContext;

不用担心你不知道NSManagedObjectContext是什么东西,我们会在随后进行解释。

但是首先我们需要配置我们的Storyboard来取消对detailViewController的使用。所以打开MainStroryboard.storyboard并且删除detail view controller:



最后我们点击FailedBanksCD.xcdatamodel文件。你看到一个可视化的编辑器。这是我们一会用来配置模型类的地方。选中屏幕中间的Entity并且删除它。

如果你的屏幕看起来并不是这样,切换编辑风格就可以了(在屏幕右下方进行切换)


好的,太棒了!我们来迅速地看一下我们的工程。如果你运行一下的话会看到一个显示空白的app.

打开FailedBanksCDAppDelegate.m。你会看到已经有一些为我们实现好的用来配置Core Data的方法了。一个方法创建了managed object context,一个方法创建了managed object model,还有一个创建了 persistent store coordinator.对吗?

不必担心你听到这些名字的时候感觉奇怪,一旦你从感性上理解了它们所代表的东西,你就会很容易地理解它们了。

  • Managed Object Model: 你可以认为这是一个数据库的纲要。 它包含了你存储在数据库中的每一个对象的定义。通常你都是使用可视化的编辑窗口来配置你数据库中的对象,包括它们的属性,它们之间的关系等。但是,你同样可以使用代码来做这样的事。
  • Persistent Store Coordinator: 你可以认为这是一个数据库连接。这是一个你创建实际的用来储存对象的数据库的名字和位置的地方。任何时候当一个managed object context想要储存一些东西的时候,都会通过这个唯一的coordinator来完成。
  • Managed Object Context: 你可以认为这是一个所有那些来自于数据库中的对象的“画板”。它同样是这三个概念中对我们最重要的一个,因为我们会和它打最多的交道。基本上,每次我们需要查找,插入和删除数据时,都会调用managed object contenxt的方法。

不必担心这些方法的调用-你并不需要大量地调用。但是如果知道有这些方法和这些方法是用来干嘛的的话,是更好地。

定义自己的模型


在Core data中你不能只访问对象的属性,只能访问整个对象。但是,如果我们将数据分成两部分- FailedBankInfo和FailedBankDetail我们可以做到相同的事情,

让我们来看看这一切是如何工作的。打开可视化模型编辑器(FailedBanksCD.xcodedatamodel)

让我们从创建一个自己的模型开始。在底下的工具栏中,点击加号按钮来创建一个新的Entity.

通过点击加号,我们创建了一个新的Entity,并且在编辑器中显示出这个Entity所有的属性。

将这个Entity命名为FailedBankInfo.然后点击这个实体保证工具节中的第三个tab标签被选中。你会看到现在显示它是NSManagedObject的子类。这是实体的默认类,我们现在会使用的一个类。随后我们会回来配置这些对象。


So let’s add some attributes. First, make sure that your Entity is selected by either clicking on the Entity name in the left panel, or the diagram for the entity in the diagram view. In the middle panel, click and hold on the plus button and then click “Add Attribute” from the popup like the following:

下面来为这个Entity添加一些属性。首先保证你选中了这个实体。在中间的工具栏中长按加号按钮并且选择"add Attribute",



在右侧的Data Model Inspector中,将这个属性命名为"name",并且将它的类型置为"string".



现在重复这个过程,并且创建两个属性"city" "state",类型同样都是String.


下面,让我们创建FailedBankDetails的实体。和之前的创建实体的步骤相同,将其命名为FailedBankDetails。然后添加如下属性:类型为integer32的zip, 类型为Date的closeDate,类型为Date的updateDate.


最后,我们需要关联这两种类型。选择FailedBankInfo,长按中间的加号按钮,但是这次选择"Add relationship"




将关系命名为"details",并且将目标置为"FailedBankDetails".



好的,刚才我们做了什么?我们建立了一个Core Data中的关系,将一个实体和另一个实体进行了关联。在这种情况下,我们创建了一个一对一的关系,每个FailedBankInfo只对应一个FailedBankDetails.在这种背景下,Core Data将会建立我们的数据库来保证FailedBankInfo表拥有一个指向FailedBankDetails对象的ID.


苹果推荐如果你使用关系使一个实体指向了另一个,那么最好再做一个相反的关系。

Add a relationship to “FailedBankDetails” named “info”, set the destination to “FailedBankInfo”, and set the inverse to “details”.

为FailedBankDetails加入一个叫"info"的关系,将目的置为FailedBankInfo,并且它的反向是details.



将两个关系的删除规则都设置为"cascade"。这意味着如果你删除了其中一个对象的话,Core Data会为你自动删除另一个相关的对象。这很好理解,因为一个FailedBankDetails不会脱离一个对应的FailedBankInfo存在。

运行一下!额?崩溃了?



让我们看看发生了什么,当你改变你的存储模型(managed Object Model)并且已经有一个persistent store 被创建了,系统就不知道该如何读取了。这就像是一个解码器在解码一个秘密信息。当你试着在改变一个信息后读取它时,就会产生一个崩溃。


你可以进行一次模型升级,但是今天会非常容易。现在仅仅需要删除存在的persistent store.最简单的方式就是在你的模拟器或者机器上将app删除。


测试我们的模型


首先,我们来为我们的数据库添加一些数据。打开FailedBanksCDAppDelegate.m并且加入如下代码,在application:didFinishLaunchingWithOptions:方法中

NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *failedBankInfo = [NSEntityDescription
    insertNewObjectForEntityForName:@"FailedBankInfo"
    inManagedObjectContext:context];
[failedBankInfo setValue:@"Test Bank" forKey:@"name"];
[failedBankInfo setValue:@"Testville" forKey:@"city"];
[failedBankInfo setValue:@"Testland" forKey:@"state"];
NSManagedObject *failedBankDetails = [NSEntityDescription
    insertNewObjectForEntityForName:@"FailedBankDetails"
    inManagedObjectContext:context];
[failedBankDetails setValue:[NSDate date] forKey:@"closeDate"];
[failedBankDetails setValue:[NSDate date] forKey:@"updateDate"];
[failedBankDetails setValue:[NSNumber numberWithInt:12345] forKey:@"zip"];
[failedBankDetails setValue:failedBankInfo forKey:@"info"];
[failedBankInfo setValue:failedBankDetails forKey:@"details"];
NSError *error;
if (![context save:&error]) {
    NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}

在第一行中,我们获得了一个纸箱managed object context的指针。


然后我们创建了一个新的NSManagedObject对象为FailedBankInfo实体。通过调用nsertNewObjectForEntityForName方法。每一个由NSManagedObject所产生的Core data对象都可以获得。一旦你有了一个对象的实例,你可以使用setValue对它在编辑器中所加入的属性进行赋值。


所以我们继续来设置test bank,为FailedBankInfo,同时也为FailedBankDetails。在这个点上对象都是被在内存中被改变的。如果需要持久化保存的话我们需要使用ManagedObjectContext调用save方法。

这就是插入数据的方法,完全没有使用SQL语句!

在我们继续试验之前,让我们来加入一些代码来打印出所有在数据库中存在的对象。

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
    entityForName:@"FailedBankInfo" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
    NSLog(@"Name: %@", [info valueForKey:@"name"]);
    NSManagedObject *details = [info valueForKey:@"details"];
    NSLog(@"Zip: %@", [details valueForKey:@"zip"]);
}


在这里我们创建了一个新的叫fetchrequest得对象。你可以认为这个对象相当于一个select语句。我们调用entityForName来获得我们想要取得的FailedBankInfo指针,并且使用setEntity来告诉我们的fetchRequest我们需要的实体类型。

然后我们调用managed object context的executeFetchRequest方法来从FailedBankInfo表中拉取所有的对象。然后我们就可以遍历每一个NSManagedObject,并且使用valueForKey来获得不同的部分。

注意即使我们所有FailedBankInfo表中的所有对象,我们仍然可以通过关系访问FailedBankDetails的对象。

这是如何工作的?在这种情景之下,当你访问一个Core Data的context中并不存在的属性的时候,它会失败,也就意味着它会跑遍整个数据库并且获取到你需要的数据!

自动生成模型文件


到目前为止,我们已经使用了NSManagedObject来和我们是实体进行工作。那并不是做事情最好的方式,因为NSmanagedObject并不是一个强类型的类。如你所见,你正在通过字符来访问实体的属性,并且那样很容易错误地输入属性的名称,也容易给属性赋值一个并不正确地类型。

更好地方式就是给每一个实体创建一个模型类,如此一来我们就有了强类型的好处,可以避免犯错误,甚至可以继承出一些子类来。你可以手动地来实现,但是XCode通过类生成器让这件工作变得非常简单。

让我们来试一下,打开 FailedBanksCD.xcdatamodel 然后选中FailedBankInfo实体,选择File\New\File。选择Core Data\NSManagedObject子类,点击Next,和Create来创建。



你可以看到在你的工程中多了一些文件:FailedBankInfo.h/m and FailedBankDetails.h/m.这些类都是很简单的,只是声明了那些你在实体中定义的属性而已。你会发现这些属性在.m文件中被定义为dynamic.因为Core Data会自动地处理这些属性。

对FailedBankDetails实体做同样的事情。



在自动生成的类中有一个问题我们必须要处理一下。看一下FailedBankDetails.h文件,你会发现info变量被正确地定位为FailedBankInfo类,但是在FailedBankInfo.h文件中,details变量被定义为NSManagedObject。



这是因为你运行FailedBankInfo的生成器在运行FailedBankDetails之前,所以它并不知道这里会有一个FailedBankDetails类的变量。

你可以手动地更改,但是最好的方法是重新生成一下FailedBankInfo的生成器,在对话框中选择“替换”。

Also, take a peek back in FailedBanksCD.xcdatamodel. When you look at the properties for the entities, you’ll notice that the classes have now been set automatically to the names of the autogenerated classes:

同时,回到FailedBanksCD.xcdatamodel.当你观察实体的变量时,你会发现这些生成的类名的变成了实体的类


现在让我们用更优雅的方式来替换FBCDAppDelegate.m中的代码,首先需要引用如下两个文件:

#import "FailedBankInfo.h"
#import "FailedBankDetails.h"
然后用如下代码替换之前的:

NSManagedObjectContext *context = [self managedObjectContext];
FailedBankInfo *failedBankInfo = [NSEntityDescription
    insertNewObjectForEntityForName:@"FailedBankInfo"
    inManagedObjectContext:context];
failedBankInfo.name = @"Test Bank";
failedBankInfo.city = @"Testville";
failedBankInfo.state = @"Testland";
FailedBankDetails *failedBankDetails = [NSEntityDescription
    insertNewObjectForEntityForName:@"FailedBankDetails"
    inManagedObjectContext:context];
failedBankDetails.closeDate = [NSDate date];
failedBankDetails.updateDate = [NSDate date];
failedBankDetails.zip = [NSNumber numberWithInt:12345];
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);
}

乍一看和之前的代码差不多,但是我们使用了自己定义的类来代替NSManagedObject.从而使代码更加地简洁和安全。

创建一个TableView

打开 FBCDMasterViewController.h 在其中加入一个failedBankInfos属性作为数据源。
@property (nonatomic, strong) NSArray *failedBankInfos;

注意它里面已经有个用来获取列表数据的managed object context了。如果你看一眼 FBCDAppDelegate.m底部的application:didFinishLaunchingWithOptions方法,你会看到managed object context在这里被配置。

切换到FBCDMasterViewController.m中并且加入如下代码

// At very top, in import section
#import "FailedBankInfo.h"
 
// At top, under @implementation
@synthesize failedBankInfos;

- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription
                                   entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSError *error;
    self.failedBankInfos = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    self.title = @"Failed Banks";
 
}

这段代码看起来和我们之前的测试代码十分相似。我们简单地创建了一个fetch request来从数据库中获取所有的FailedBankInfos然后将其储存在我们的成员变量中。

在 numberOfSectionsInTableView: 方法中返回1

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

替换 numberOfRowsInSection方法为如下样子

- (NSInteger)tableView:(UITableView *)tableView
    numberOfRowsInSection:(NSInteger)section {
    return [failedBankInfos count];
}

按如下方式修改 cellForRowAtIndexPath方法:

- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    // Set up the cell...
    FailedBankInfo *info = [failedBankInfos objectAtIndex:indexPath.row];
    cell.textLabel.text = info.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@",
        info.city, info.state];
 
    return cell;
}


最后选中MainStoryboard.storyboard并且选中Master View Controller.,将tableview cell的style置为subtitle.


编译并运行程序,你会看到如下现象:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值