iOS CoreData 编程


iOS CoreData 编程

内容概述

  • CoreData简介
  • 使用Xcode模板创建CoreData项目
  • 使用CoreData实现数据的增、删、查、改
  • CoreData数据在UITableView中展现

23.1 CoreData简介

传统的编程中我们使用普通文件或者Sqlite数据库保存数据,普通文件给查询数据带来不便,而Sqlite数据库对于没有数据库经验的程序员来说也是一种挑战。

苹果在SDK3.0之后提供了CoreData框架,这可以说给程序员们带来了福音。CoreData框架以面向对象的方式来操作数据库。后台的数据库对于程序是完全透明的,即使你不懂数据库也不懂SQL语句也可以开发出像样的基于数据库的应用程序。

使用CoreData框架编程时,几个核心的类是必须要掌握的,它们是:

  1. NSPersistentStoreCoordinator,持久存储调停者,是应用程序和物理数据库之间的桥梁。
  2. NSManagedObjectContext,管理对象上下文,是程序员和应用程序之间的桥梁,使用该上下文可以实现数据的增、删、查、改功能。
  3. NSManagedObjectModel管理对象模型,映射数据库中表结构。
  4. NSManagedObject管理对象,映射表中的一行。
  5. NSEntityDescription,实体描述类,关联数据库中的表,实例化表中的一个对象是使用。
  6. NSFetchRequest,查询请求类,在查询时使用。
  7. NSPredicate,定义查询条件。
  8. NSSortDescriptor,排序描述符。

下面我们分析一下这些类之间的关系:

  1. 物理文件、持久存储和上下文之间的关系。

图1.1 物理文件、持久存储及上下文之间的关系

  1. 下图是查询和条件及排序直接的关系。

图1.2下图是查询和条件及排序直接的关系

23.2 使用Xcode模板创建CoreData项目

进行CoreData的基本配置也是一个繁琐的过程,幸好Xcode提供了创建CoreData项目的模板。使用Xcode模板创建项目的过程如下:

  1. 启动Xcode创建一个空项目。

图1.3 创建一个空的项目

  1. 在下一步的对话框中选择"Use Core Data"选项。

图1.4 选择使用Core Data

  1. 在代理的头文件中生产了如下代码。
#import <UIKit/UIKit.h>

@interface AmakerAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

// 管理上下文

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

// 管理对象模型

@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;

// 持久存储

@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

// 保存上下文

- (void)saveContext;

// 应用程序文档目录

- (NSURL *)applicationDocumentsDirectory;

@end

这里声明了管理对象上下文属性、管理对象模型属性、持久存储属性、保存上下文方法和获得应用程序文档目录的方法。

  1. 下面是.m文件中的实现。

4.1 获得sqlite数据库保存路径方法

// 返回应用程序路径

- (NSURL *)applicationDocumentsDirectory

{

    // 应用程序路径

    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];

}

4.2 实例化持久存储,并建立持久存储和物理文件之间的关系。

// 根据物理文件的位置,获得持久存储

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator

{

    if (\_persistentStoreCoordinator != nil) {

        return \_persistentStoreCoordinator;

    }

    // 找到物理文件的位置

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"chapter23\_01.sqlite"];

    NSError *error = nil;

    // 实例化持久存储

    \_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    // 建立持久存储和物理文件之间的关联

    if (![\_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&amp;error]) {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

        abort();

    }

    return \_persistentStoreCoordinator;

}

4.3 实例化管理对象模型

// 如果管理对象模型不存在,则从应用程序文件中创建它

- (NSManagedObjectModel *)managedObjectModel

{

    if (\_managedObjectModel != nil) {

        return \_managedObjectModel;

    }

    // 获得模型文件的URL

    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"chapter23\_01" withExtension:@"momd"];

    // 根据URL实例化管理对象模型

    \_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    return \_managedObjectModel;

}

4.4 创建上下文,并绑定持久存储。

// 如果上下文不存在,则创建它并绑定持久存储

- (NSManagedObjectContext *)managedObjectContext

{

    if (\_managedObjectContext != nil) {

        return \_managedObjectContext;

    }

    // 获得持久存储

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil) {

        // 实例化上下文

        \_managedObjectContext = [[NSManagedObjectContext alloc] init];

        // 设置上下文和持久存储的关系

        [\_managedObjectContext setPersistentStoreCoordinator:coordinator];

    }

    return \_managedObjectContext;

}

4.5 保存上下文

// 保存上下文

- (void)saveContext

{

    NSError *error = nil;

    // 管理上下文

    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;

    if (managedObjectContext != nil) {

        // 在上下文中有改变但是没有保存,则保存

        if ([managedObjectContext hasChanges] &amp;&amp; ![managedObjectContext save:&amp;error]) {

            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

            abort();

        }

    }

}

4.6 最后是数据模型界面

23.3 使用CoreData实现数据的增、删、查、改

本节我们以客户和订单为模型,使用CoreData框架实现客户和订单数据的增、删、查、改。创建步骤如下:

  1. 使用Xcode模板创建一个空项目,并且勾选"Use Core Data"选项。

图23.6 选择使用Core Data

  1. 创建一个CoreData工具类,声明一组维护客户的订单的方法。
#import <Foundation/Foundation.h>

@interface CoreDataUtil : NSObject

// 添加客户

-(void)addCustomer;

// 删除客户

-(void)deleteCustomer;

// 更新客户

-(void)updateCustmer;

// 查询客户

-(void)queryCustomer;

// 添加订单

-(void)addOrder;

// 删除订单

-(void)deleteOrder;

// 更新订单

-(void)updateOrder;

// 查询订单

-(void)queryOrder;

// 根据客户查询订单

-(void)queryOrderByCustomer;

@end
  1. 实现CoreData工具类的初始化方法,该方法从应用程序代理中获得NSManagedObjectContext对象,并赋值给前面声明的静态NSManagedObjectContext实例。
// 声明静态 NSManagedObjectContext

static NSManagedObjectContext *context;

@implementation CoreDataUtil

// 初始化方法

- (id)init

{

    self = [super init];

    if (self) {

        // 从AmakerAppDelegate中获得NSManagedObjectContext实例

        if (context==nil) {

            AmakerAppDelegate *delegate = [UIApplication sharedApplication].delegate;

            context = delegate.managedObjectContext;

        }

    }

    return self;

}
  1. 添加客户的实现,添加客户使用NSEntityDescription的静态方法insertNewObjectForEntityForName,创建一个客户对象,该方法有两个参数,第一个参数是要创建对象的实体名称,第二个参数是NSManagedObjectContext实例。为客户对象的属性赋值,并调用NSManagedObjectContext的save方法保存对象到数据库。
// 添加客户

-(void)addCustomer{

    // 使用NSEntityDescription的静态方法insertNewObjectForEntityForName,创建一个客户对象

    Customer *c = [NSEntityDescription insertNewObjectForEntityForName:@"Customer" inManagedObjectContext:context];

    if (c!=nil) {

        // 为客户属性赋值

        c.age = [NSNumber numberWithInt:20];

        c.name = @"tom";

        // 使用NSManagedObjectContext保存客户

        BOOL result = [context save:nil];

        // 判断是否保存成功

        if (result) {

            NSLog(@"Save OK!");

        }else{

            NSLog(@"Save Fail!");

        }

    }

}
  1. 删除客户,删除客户首先查询到要删除的客户,查询使用NSManagedObjectContext对象的executeFetchRequest方法,该方法需要一个NSFetchRequest对象,该对象可以直接实例化,并为其指定要查询的实体对象,即NSEntityDescription实例。查询到该对象后,调用NSManagedObjectContext对象的delete方法删除对象,并调用save方法保存。
// 删除客户

-(void)deleteCustomer{

    [self addCustomer];

    // 实例化NSFetchRequest,用来查询

    NSFetchRequest *request = [[NSFetchRequest alloc]init];

    // 通过实体名称实例化NSEntityDescription对象

    NSEntityDescription *c = [NSEntityDescription entityForName:@"Customer" inManagedObjectContext:context];

    // 设置查询对象

    [request setEntity:c];

    // 进行查询

    NSArray *customers = [context executeFetchRequest:request error:nil];

    // 如果查询结果大于0,获得第一个对象,并删除

    if ([customers count]>0) {

        // 获得集合中的第一个对象

        Customer *c1 = [customers objectAtIndex:0];

        // 删除之

        [context deleteObject:c1];

        // 保存

        BOOL result = [context save:nil];

        // 判断结果

        if (result) {

            NSLog(@"Delete OK!");

        }else{

            NSLog(@"Delete Fail!");

        }

    }

}
  1. 更新客户,和查询类似,首先查询到要更新的对象,重新设置属性,并调用save方法保存
-(void)updateCustmer{

    [self addCustomer];

    // 实例化NSFetchRequest对象

    NSFetchRequest *request = [[NSFetchRequest alloc]init];

    // 获得NSEntityDescription实例

    NSEntityDescription *c = [NSEntityDescription entityForName:@"Customer" inManagedObjectContext:context];

    // 设置查询实体

    [request setEntity:c];

    // 执行查询

    NSArray *customers = [context executeFetchRequest:request error:nil];

    if ([customers count]>0) {

        Customer *c1 = [customers objectAtIndex:0];

        // 重新设置属性

        [c1 setAge:[NSNumber numberWithInt:100]];

        [c1 setName:@"new name"];

        // 保存

        BOOL result = [context save:nil];

        // 判断结果

        if (result) {

            NSLog(@"update OK!");

        }else{

            NSLog(@"update Fail!");

        }

    }

}
  1. 订单的添加、删除、修改都和客户维护一致,这里不再赘述,下面来看,根据客户条件查询订单。这里使用NSPredicate指定查询条件。这里使用该实例的predicateWithFormat格式化方法指定查询条件。
// 根据客户,条件查询订单

-(void)queryOrderByCustomer{

    // 实例化NSFetchRequest

    NSFetchRequest *request = [[NSFetchRequest alloc]init];

    // 根据实体类名称,获得NSEntityDescription实例

    NSEntityDescription *c = [NSEntityDescription entityForName:@"Customer" inManagedObjectContext:context];

    // 设置查询实体

    [request setEntity:c];

    // 执行查询,获得第一个客户

    NSArray *customers = [context executeFetchRequest:request error:nil];

    Customer *c1;

    if ([customers count]>0) {

        c1 = [customers objectAtIndex:0];

    }

    // 实例化NSFetchRequest

    NSFetchRequest *request2 = [[NSFetchRequest alloc]init];

    // 根据实体类名称,获得NSEntityDescription实例

    NSEntityDescription *o = [NSEntityDescription entityForName:@"Order" inManagedObjectContext:context];

    // 实例化NSPredicate

    NSPredicate *p = [NSPredicate predicateWithFormat:@"customer=%@",c1];

    // 为请求指定条件

    request2.predicate = p;

    // 设置查询实体

    [request2 setEntity:o];

    //执行查询

    NSArray *orders = [context executeFetchRequest:request error:nil];

    // 遍历,输出

    for(Order *o in orders){

        NSLog(@"%@",o.name);

    }

}

在xib文件中添加若干按钮,添加单击事件方法,调用CoreData工具类方法,查看执行结果。

图23.7 使用CoreData维护客户和订单

23.4 CoreData数据在UITableView中展现

本节在上一节的基础之上,增加UI界面,使用UITableView来可视化的维护数据。在xib文件中添加UITableView,使用该视图展示数据,另外,在导航栏增加添加和删除按钮。点击按钮添加或删除数据。我们先来看一下程序的运行结果一睹为快吧。

图23.7  使用CoreData和UITableView维护数据

程序的实现步骤如下:

  1. 创建一个项目,在界面上添加一个UITableView,并在导航栏上添加两个按钮。

  2. 在.h控制器中实现UITableViewDataSource、UITableViewDelegate和UIAlertViewDelegate协议,添加CoreData工具类属性、数据源属性、表格视图属性和添加删除按钮。

#import <UIKit/UIKit.h>

#import "CoreDataUtil.h"

// 实现UITableViewDataSource、UITableViewDelegate和UIAlertViewDelegate协议

@interface AmakerRootViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>

// CoreData工具类

@property(nonatomic,strong)CoreDataUtil *util;

// 数据源

@property(nonatomic,strong)NSMutableArray *dataSource;

// 表格视图

@property (strong, nonatomic) IBOutlet UITableView *tableView;

// 添加删除按钮

@property(nonatomic,strong)UIBarButtonItem *addItem,*delItem;

@end
  1. 在viewDidLoad方法中,实例化CoreDataUtil,根据查询客户列表数组初始化数据源,实例化添加、删除按钮。
- (void)viewDidLoad

{

    [super viewDidLoad];

    // 实例化CoreDataUtil

    self.util = [[CoreDataUtil alloc]init];

    // 查询客户列表

    NSArray *array = [self.util queryCustoer];

    // 初始化数据源

    self.dataSource = [NSMutableArray arrayWithArray:array];

    // 实例化添加、删除按钮

    self.addItem = [[UIBarButtonItem alloc]initWithTitle:@"Add" style:UIBarButtonItemStylePlain target:self action:@selector(add)];

    self.navigationItem.rightBarButtonItem = self.addItem;

    self.delItem = [[UIBarButtonItem alloc]initWithTitle:@"Del" style:UIBarButtonItemStylePlain target:self action:@selector(del)];

    self.navigationItem.leftBarButtonItem = self.delItem;

}
  1. 点击添加按钮,显示添加客户对话框,设置对话框属性,使得用户可以输入客户名称。
// 添加方法

-(void)add{

    // 显示对话框

    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Add Customer" message:nil delegate:self cancelButtonTitle:@"Add" otherButtonTitles:nil, nil];

    alert.alertViewStyle = UIAlertViewStylePlainTextInput;

    [alert show];

}
  1. 点击删除按钮,设置表格视图的编辑属性。
// 删除方法,设置编辑属性

-(void)del{

    if (![self.tableView isEditing]) {

        self.delItem.title=@"Done";

        [self.tableView setEditing:YES];

    }else{

        self.delItem.title=@"Del";

        [self.tableView setEditing:NO];

    }

}
  1. 在表格视图代理的commitEditingStyle方法中删除客户信息,首先获得要删除的客户信息,从数据源删除数据,从数据库删除数据,重新加载表视图。
// 删除数据

-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{

    // 获得当前数据

    Customer *c = [self.dataSource objectAtIndex:[indexPath row]];

    // 从数据源数组删除

    [self.dataSource removeObject:c];

    // 从数据库删除

    [self.util deleteCustomer:c];

    // 重新加载表视图

    [self.tableView reloadData];

}
  1. 实现对话框的代理方法,通过判断按钮标题添加或更新客户信息。获得输入框内容,调用CoreData工具类来实现添加或删除。
// 对话框代理方法,实现添加数据

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{

    // 获得按钮标题

    NSString *title = [alertView buttonTitleAtIndex:0];

    // 获得输入框

    UITextField *nameTf = [alertView textFieldAtIndex:0];

    // 获得输入框内容

    NSString *name = nameTf.text;

    // 添加

    if ([title isEqualToString:@"Add"]) {

        [self.util addCustomer:name];

    }else{

        // 更新

        self.currentCustomer.name = name;

        [self.util updateCustomer:self.currentCustomer];

    }

    // 重新加载表视图

    [self.tableView reloadData];

}
  1. 实现表格视图的数据源方法,根据客户列表数,返回表格行数,获得表格单元。
// 根据客户列表数,返回表格行数

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    NSArray *array = [self.util queryCustoer];

    self.dataSource = [NSMutableArray arrayWithArray:array];

    return [self.dataSource count];

}

// 获得表格单元

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cid"];

    if (cell==nil) {

        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cid"];

    }

    Customer *c = [self.dataSource objectAtIndex:[indexPath row]];

    cell.textLabel.text = c.name;

    return cell;

}
  1. 实现选中行,编辑客户信息功能,弹出对话框,显示要更新的数据。
// 选中行,修改客户信息

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    // 弹出对话框,显示要更新的数据

    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Update Customer" message:nil delegate:self cancelButtonTitle:@"Update" otherButtonTitles:nil, nil];

    alert.alertViewStyle = UIAlertViewStylePlainTextInput;

    UITextField *nameTf = [alert textFieldAtIndex:0];

    Customer *c = [self.dataSource objectAtIndex:[indexPath row]];

    self.currentCustomer = c;

    nameTf.text = c.name;

    [alert show];

}