简单介绍
1.Core Data 是数据持久化存储的最佳方式
2.数据最终的存储类型可以是:SQLite数据库,XML,二进制,内存里,或自定义数据类型
在Mac OS X 10.5Leopard及以后的版本中,开发者也可以通过继承NSPersistentStore类以创建自定义的存储格式
3.好处:能够合理管理内存,避免使用sql的麻烦,高效
4.构成:
(1)NSManagedObjectContext(被管理的数据上下文)
操作实际内容(操作持久层)
作用:插入数据,查询数据,删除数据
(2)NSManagedObjectModel(被管理的数据模型)
数据库所有表格或数据结构,包含各实体的定义信息
作用:添加实体的属性,建立属性之间的关系
操作方法:视图编辑器,或代码
(3)NSPersistentStoreCoordinator(持久化存储助理)
相当于数据库的连接器
作用:设置数据存储的名字,位置,存储方式,和存储时机
(4)NSManagedObject(被管理的数据记录)
相当于数据库中的表格记录
(5)NSFetchRequest(获取数据的请求)
相当于查询语句
(6)NSEntityDescription(实体结构)
相当于表格结构
(7)后缀为.xcdatamodeld的包
里面是.xcdatamodel文件,用数据模型编辑器编辑
编译后为.momd或.mom文件
创建CoreData工程
系统给我们提供了一个类似记事本界面的CoreData模板,在创建的时候可以选择Master-Detail Application项创建该模板工程:
本例将创建一个空白工程,记得一定要在勾选Use Core Data选项:
创建完毕后可以看到目录里有一个.xcdatamodeld文件,这个文件是一个可视化的数据模型结构文件,可以在这里操作数据模型,比如创建实体(实际其实就是表)、给实体添加属性、建立多个实体之间的关系等等。当前创建了两个实体:学生和成绩,并在学生实体中创建了一个与成绩实体的relationship。最终两个实体的属性结构为:
- StudentTable(name, age, record)
- RecordTable (coursr, grade)
此次Demo主要围绕数据库的增删改查操作来介绍CoreData的使用,要实现的界面效果如下:
以点击“插入”为例,在数据库中生成一条记录,由于之前的设定是将学生信息和成绩分别存放在两个实体即两个表中,分别查看两表发现数据已成功植入。对于数据库管理软件,我个人使用Navicat,它支持几乎所有的主流数据库。
代码解析
先看看AppDelegate.h文件:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface AppDelegate : 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
系统自动生成了3个接口属性managedObjectContext、managedObjectModel、persistentStoreCoordinator以及两个接口方法saveContext和applicationDocumentsDirectory。其中这三个属性所属的类是CoreData中非常重要的3个类,堪称“三剑客”。
NSManagedObjectModel(被管理的数据模型)
可以将这个东西看作是数据库的轮廓,或者结构。这里包含了各个实体的定义信息,一般来说,你会使用我们刚刚看过的视觉编辑器来操作这个物体,添加属性,建立属性之间的关系等等,当然你也可以使用代码
NSPersistentStoreCoordinator(持久性数据协调器)
可以将这个东西看作是数据库连接库,在这里,你将设置数据存储的名字和位置,以及数据存储的时机
NSManagedObjectContext(被管理的对象上下文)
可以将这一部分看作是数据的实际内容,这也是整个数据库中对我们而言最重要的部分,对数据的增删改查操作都在这里完成。
更通俗地理解这个“三剑客”,分别用于:数据库定义、数据库连接、数据库操作。
saveContext方法用于将上下文中的更改持久化保存到磁盘中(NSManagedObjectContext实际上维护的是一段内存,对它的操作结果更新的只是内存中的数据,如果不将其保存到磁盘,操作等于百搭!)。这一方法在即将终止app进程的时候调用。
applicationDocumentsDirectory方法用于返回存储数据库文件的路径,这将在构建持久性数据协调器的时候用到。
ok!现在来看AppDelegate.m文件:
由于本例是通过storyboard在进入程序时加载视图的,因此didFinishLaunchingWithOptions方法中并没有创建视图控制器,而是获取到了作为主要视图控制器的ViewController,并使其managedObjectContext属性引用了AppDelegate中的该属性。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *vc = (ViewController *)self.window.rootViewController;
vc.managedObjectContext = self.managedObjectContext;
return YES;
}
这一做法不仅在主界面控制器ViewController方便获取managedObjectContext即上下文来进行一些操作,还通过self.managedObjectContext调用了managedObjectContext属性的getter方法。来看这个方法:
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
可以看到方法中实例化了一个NSManagedObjectContext对象,并将其与一个持久化数据协调器对象coordinator绑定,然后返回。实现中调用了persistentStoreCoordinator方法,可以猜到这个方法是用来实例化一个NSPersistentStoreCoordinator对象的。那就顺藤摸瓜来看一下:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreData_1_.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
果不其然!方法中通过一个NSManagedObjectModel对象实例化了一个NSPersistentStoreCoordinator对象,并配置了数据库类型、存储路径等信息。追踪到managedObjectModel方法:
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData_1_" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
方法中通过对象模型文件实例化了一个NSManagedObjectModel对象。值得注意的是对应文件的后缀是“momd”,而不是“xcdatamodeld”,那是因为程序编译后会在bundle目录里生成一个momd的文件,而直接查看xcdatamodeld文件的内容,可以发现里面是空的。
至此系统自动生成的代码都介绍完毕了,接下来就来看看我们是怎么根据传入的managedObjectContext对象在主视图控制器中实现数据库的“增删改查操作”的。由于代码功能逻辑简单,我就直接贴出全部代码,在代码中配合注释来说明好了:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *tfName;
@property (weak, nonatomic) IBOutlet UITextField *tfAge;
@property (weak, nonatomic) IBOutlet UITextField *tfCourse;
@property (weak, nonatomic) IBOutlet UITextField *tfGrade;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
/*插入数据*/
- (IBAction)actionInsert:(id)sender {
NSString *name = self.tfName.text;
NSString *age = self.tfAge.text;
NSString *course = self.tfCourse.text;
NSString *grade = self.tfGrade.text;
if (name.length != 0 && grade.integerValue >= 0 && grade.integerValue <= 100 && course.length != 0 && age.length != 0) {
/*!
* 在上下文中插入一个NSManagedObject对象并获取该对象
* NSManagedObject 该类为最接近数据的类!其实例为被管理的对象,对应数据库表中的一条记录
* NSEntityDescription 实体描述类,可以理解成描述表结构的类,对应一个表
*
* 从方法名上来看似乎已经将对象插入到上下文中了,你可能会想我们还没给对象的各个属性设值呢!的确是这样,不过不要紧,因为前面说过上下文中数据的改变对应的是内存中数据的更新,此时还没有持久化到磁盘中呢!只要在保存到磁盘之前对该NSManagedObject对象的相关属性设值就行了!接下来要做的工作就不言而喻了----设值、持久化!
*/
NSManagedObject *studentObject = [NSEntityDescription insertNewObjectForEntityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
/*设值*/
[studentObject setValue:name forKey:@"name"];
[studentObject setValue:@(age.integerValue) forKey:@"age"];
NSManagedObject *recordObject = [NSEntityDescription insertNewObjectForEntityForName:@"RecordTable" inManagedObjectContext:self.managedObjectContext];
[recordObject setValue:course forKey:@"course"];
[recordObject setValue:@(grade.integerValue) forKey:@"grade"];
[studentObject setValue:recordObject forKey:@"record"];
/*将上下文中的更新持久化到磁盘*/
NSError *err = nil;
if (![self.managedObjectContext save:&err]) {
NSLog(@"%@",err.localizedDescription);
abort();
}else{
[[[UIAlertView alloc]initWithTitle:nil message:@"Insert Successfully!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
}
}
}
/*删除数据*/
- (IBAction)actionDelete:(id)sender {
/*!
* 通过上下文来删除某个被管理的对象,即删除数据库表中的某条记录,调用的方法为deleteObject:
* 可想而知当务之急是获取到需要删除的那个NSMangedObject类型的对象!因此在删除之前,要做的工作就是从磁盘数据库中fetch到那个对象,也就是一个查询操作。事实上除了插入操作,删除、更新操作都需要实现进行查询操作以获取到需要操作的数据对象或者数据对象集合,因此完全可以封装一个查询的操作,这里为了避免避重就轻就没有封装。
* 接下来要做的是:查询、删除、持久化
*/
/*查询*/
//实例化一个请求对象
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
//指定从哪个实体即哪个表查询
NSEntityDescription *studentTable = [NSEntityDescription entityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:studentTable];
//执行查询
NSError *error = nil;
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(@"%@",error.localizedDescription);
return;
}
if (fetchResults.count == 0) {
[[[UIAlertView alloc]initWithTitle:nil message:@"No such record!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
return;
}
/*删除*/
for (NSManagedObject *studentObject in fetchResults) {
[self.managedObjectContext deleteObject:studentObject];
}
/*持久化*/
if (![self.managedObjectContext save:&error]) {
NSLog(@"%@",error.localizedDescription);
abort();
}else{
[[[UIAlertView alloc]initWithTitle:nil message:@"Delete Successfully!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
}
}
/*更新数据*/
- (IBAction)actionUpdate:(id)sender {
/*!
* 和删除操作一样,同样需要从磁盘中获取到要更新的数据对象,查询方式一致
* 更新流程:查询、更新、持久化
*/
/*查询*/
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *StudentTable = [NSEntityDescription entityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:StudentTable];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"lotheve1"];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(@"%@",error.localizedDescription);
return;
}
if (fetchResults.count == 0) {
[[[UIAlertView alloc]initWithTitle:nil message:@"No such record!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
return;
}
/*更新*/
for (NSManagedObject *studentObjecct in fetchResults) {
[studentObjecct setValue:@(80) forKey:@"age"];
}
/*持久化*/
if (![self.managedObjectContext save:&error]) {
NSLog(@"%@",error.localizedDescription);
abort();
}else{
[[[UIAlertView alloc]initWithTitle:nil message:@"Update Successfully!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
}
}
/*查询数据*/
- (IBAction)actionQuery:(id)sender {
/*!
* 前面已经介绍过查询了,这里就不再赘述。要知道的是,查询其实是一个数据从磁盘到内存的过程,持久化则是内存到磁盘的过程。
*/
NSFetchRequest *fetchReqest = [[NSFetchRequest alloc]init];
NSEntityDescription *studentTable = [NSEntityDescription entityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
[fetchReqest setEntity:studentTable];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"lxx"];
[fetchReqest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:fetchReqest error:&error];
if (error) {
NSLog(@"%@",error.localizedDescription);
return;
}
if (fetchResults.count == 0) {
[[[UIAlertView alloc]initWithTitle:nil message:@"No such record!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
return;
}
//遍历查询到的结果
for (NSManagedObject *studentObjecct in fetchResults) {
id name = [studentObjecct valueForKey:@"name"];
id age = [studentObjecct valueForKey:@"age"];
NSManagedObject *recordObject = [studentObjecct valueForKey:@"record"];
id course = [recordObject valueForKey:@"course"];
id grade = [recordObject valueForKey:@"grade"];
NSLog(@"name:%@, age:%@, course:%@, grade:%@",name,age,course,grade);
}
[[[UIAlertView alloc]initWithTitle:nil message:@"Query Successfully!" delegate:nil cancelButtonTitle:@"Okay!" otherButtonTitles: nil] show];
}
@end
建立对象关系映射(ORM)
在工程中,我通过可视化界面建立的实体,其所属的类均属于NSManagedObject,在代码中要获取一个实体的一条记录,则是实例化一个NSManagedObject对象。这样有一个通病,就是不易区分各个实体对象,我们顶多通过命名来区分,比如studentObjecct、recordObject。
事实上,我们可以给每一个实体建立一个模型,这个模型类继承自NSManagedObject,并拥有其对应实体的属性作为其类的属性。
创建一个NSManagedObject subclass文件,并勾选要创建模型的实体。
创建完毕后,工程中多了8个文件,两个类文件两个Category文件。可以看到这两个类分别对应xcdatamodeld文件中的两个实体,集成自NSManagedObject;另外两个Category文件中声明了响应的属性。
再来看一下xcdatamodeld文件中的两个实体,发现其所属的类已经自动连接到了刚刚建立的两个类:
接着就可以在代码中使用这两个类了。对插入模块中的这段代码进行调整
NSManagedObject *studentObject = [NSEntityDescription insertNewObjectForEntityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
[studentObject setValue:name forKey:@"name"];
[studentObject setValue:@(age.integerValue) forKey:@"age"];
NSManagedObject *recordObject = [NSEntityDescription insertNewObjectForEntityForName:@"RecordTable" inManagedObjectContext:self.managedObjectContext];
[recordObject setValue:course forKey:@"course"];
[recordObject setValue:@(grade.integerValue) forKey:@"grade"];
[studentObject setValue:recordObject forKey:@"record"];
调整后
StudentTable *studentObject = [NSEntityDescription insertNewObjectForEntityForName:@"StudentTable" inManagedObjectContext:self.managedObjectContext];
studentObject.name = name;
studentObject.age = @([age integerValue]);
RecordTable *recordObject = [NSEntityDescription insertNewObjectForEntityForName:@"RecordTable" inManagedObjectContext:self.managedObjectContext];
recordObject.course = course;
recordObject.grade = @([grade integerValue]);
studentObject.record = recordObject;
虽然代码量上没多少优势,但是可读性更强,操作性更高!其他模块请自行调整代码。
其他
可以设置数据库在操作过程中打印相关的sql语句:
Product—-Scheme—-Edit Scheme—-Run—-Arguments
在Arguments Passed On Lanuch中添加参数:
-com.apple.CoreData.SQLDebug 1
总结
Core Data框架基本的5个类::NSPersistentStoreCoordinator、NSManagedObjectContext、NSManagedObjectModel、NSEntityDescription、NSManagedObject。
NSPersistentStoreCoordinator持久化存储协调器(简称协调器):负责从磁盘加载数据和将数据写入磁盘。协调器可以处理多种格式的数据库文件(NSPersistentStore),如二进制文件,XML文件、SQLite文件。
NSEntityDescription实体描述(简称实体):相当于表结构类,可以被看做是NSManagedObject对象的“class”。实体定义了一个NSManagedObject对象所拥有的所有属性(NSAttributeDescription),关系(NSRelationshipDescription),提取属性(NSFetchedPropertyDescription)。
NSManagedObjectContext托管对象上下文(简称上下文):上下文是内存中的一块暂存区域。查询对象(使用NSFetchRequest),创建对象,删除对象等操作都是在上下文中进行。在上下文没有保存之前,对数据的任何修改都只记录在暂存区中,不会影响磁盘上的数据。你可以创建多个上下文,但整个程序只能创建一个NSPersstentStoreCoordinator对象。
NSManagedObject托管对象:Core Data的核心单元,其实例相当于表中的一条记录。模型对象的数据被持有在NSManagedObject对象中。每一个NSManagedObject对象都对应一个实体(就像每一个对象都有一个类)
NSManagedObjectModel托管对象模型:NSManagedObjectModel通常被定义在一个.mom文件中,文件中保存了所有实体的定义。可以看做是整个持久化数据库的结构模型。
参考文档:
《玉令天下的博客》
《iphone数据存储之-- Core Data的使用(一)》
《Core Data 概述》