【一】前言
Core Data框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,不需要编写任何SQL语句。使用此功能,要添加CoreData.framework和导入主头文件 <CoreData/CoreData.h>。
【二】各种类作用的介绍
创建Core Data Stack
- iOS10中利用NSPersistentContainer
- iOS10之前涉及NSManagedObjectContext、NSPersistentStoreCoordinator、NSManagedObjectModel、NSPersistentStore这些类
【三】手动创建CoreData数据
我们创建一个和平常一样的工程,不需要勾选Use Core Data
:
一、创建模型文件
1、进入创建新文件,command+N
或者如下图
2、选择文件类型, 如下图:
3、设置文件名,如下图:
4、模型文件创建成功,会出现以后
建好后你会发现工程中多了 XXXXXXX.xcdatamodeld,我们需要在这里添加实体(首字母大写)和实体的属性。
二、创建实体
1、利用可视化的方式创建实体,实体的功能就类似于我们的Model类,具体操作如下如:
在传统的项目中我们都使用OC变成,但是CoreData默认使用的是Swift语言,所以我们要设置回来OC,详情见图片
同时需要将codegen选为Manaul/None
这里我们需要创建Person和Card的实体以及实体属性:
实体间的关系:选中Person实体,在Person中添加card属性:
选中Card实体,在Card中添加person属性:
添加完成后,他们关系如下:
三、创建实体类
利用可视化创建了实体,但是我们要想获取对应的数据和名称,就必须关联类,因此要创建实体类,创建步骤如下:
1、选中 .xcdatamodeld 文件通过 Editor 创建:NSManagedObject subclass
类文件
2、生成了4个分类
分别为A+CoreDataClass.h, A+CoreDataClass.m, A+CoreDataProperties.h,A+CoreDataProperties.m
前2个为正式类文件(可以在需要用的地方直接引用这个类,这个类内部已经引用了后面两个类), 后两个为属性类文件。
四、手动创建CoreData的使用
值得注意的是:下面的例子中我们可以直接使用创建的目的实体类如:Dog,也可以使用NSManagedObject 这一公共实体类,可以使用KVC赋值,也可以使用 . 属性 的方式直接赋值。
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
[person setValue:@"lifengfeng" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
+++++++++++++++++ 另一个方式 +++++++++++++++++
Dog *dog = [NSEntityDescription insertNewObjectForEntityForName:@"Dog" inManagedObjectContext:self.myContext];
dog.name = @"name1";
dog.age = @"12";
for (Dog *obj in objs) {
NSLog(@"name=%@", obj.name);
}
1、搭建上下文环境
//1、创建模型对象
//获取模型路径
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"NewCodeDataModel" withExtension:@"momd"];//NewCodeDataModel.xcdatamodeld
//根据模型文件创建模型对象
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//2、创建持久化助理
//利用模型对象创建助理对象
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
//数据库的名称和路径
NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlPath = [docStr stringByAppendingPathComponent:@"mySqlite.sqlite"];
NSLog(@"path = %@", sqlPath);
NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
//设置数据库相关信息
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:nil];
//3、创建上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
//关联持久化助理
[context setPersistentStoreCoordinator:store];
self.myContext = context;
其中
持久化存储库的类型(addPersistentStoreWithType:参数):
(1)NSSQLiteStoreType SQLite数据库
(2)NSBinaryStoreType 二进制平面文件
(3)NSInMemoryStoreType 内存库,无法永久保存数据
ConcurrencyType可选项(initWithConcurrencyType:参数):
(1)NSConfinementConcurrencyType 这个是默认项,每个线程一个独立的Context,主要是为了兼容之前的设计。
(2)NSPrivateQueueConcurrencyType 创建一个private queue(使用GCD),这样就不会阻塞主线程。
(3)NSMainQueueConcurrencyType 创建一个main queue,使用主线程,会阻塞。
2、增:增加数据
/**
增加数据
*/
-(void)addData{
//传入上下文,创建一个Person实体对象:
NSManagedObject *person =
[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
//设置简单属性:
[person setValue:@"lifengfeng" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
//传入上下文,创建一个Card实体对象:
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:_context];
[card setValue:@"1234567890" forKey:@"no"];
//设置Person和Card之间的关联关系:
[person setValue:card forKey:@"card"];
//利用上下文对象,将数据同步到持久化存储库:
NSError *error = nil;
BOOL success = [_context save:&error];
if (!success) {
[NSException raise:@"访问数据库错误!" format:@"%@", [error localizedDescription]];
}else{
NSLog(@"访问数据库成功!");
}
// 如果是想做更新操作:只要在更改了实体对象的属性后调用[context save:&error],就能将更改的数据同步到数据库
}
3、删:删除数据
/**
删除数据
*/
-(void)deleteData{
//建立请求,连接实体
NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = person;
//设置条件过滤(搜索name属性中包含”lifengfeng“的那条记录,注意等号必须加,可以有空格,也可以是==)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", @"lifengfeng"];
request.predicate = predicate;
//遍历所有实体,将每个实体的信息存放在数组中
NSArray *arr = [_context executeFetchRequest:request error:nil];
//删除并保存
if(arr.count)
{
for (NSEntityDescription *p in arr)
{
[_context deleteObject:p];
NSLog(@"删除%@成功!",p.name);
}
//保存
[_context save:nil];
}
}
4、改:修改数据
/**
修改数据
*/
-(void)updateData{
//建立请求,连接实体
NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = person;
//设置条件过滤(搜索所有name属性不为“lifengfeng”的数据)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name!=%@", @"lifengfeng"];
request.predicate = predicate;
//遍历所有实体,将每个实体的信息存放在数组中
NSArray *arr = [_context executeFetchRequest:request error:nil];
//更改并保存
if(arr.count)
{
for (NSEntityDescription *p in arr)
{
p.name = @"更改";
}
//保存
[_context save:nil];
}
else
{
NSLog(@"无检索");
}
}
5、查:查询数据
/**
查询数据
*/
-(void)queryData{
//初始化一个查询请求:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//设置要查询的实体:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = entity;
//设置排序(按照age降序):
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
//设置条件过滤(name like '%lifengfeng%'):
//设置条件过滤时,数据库里面的%要用*来代替
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*lifengfeng*"];
request.predicate = predicate;
//执行请求:
NSError *error = nil;
NSArray *objs = [_context executeFetchRequest:request error:&error];
if (error) {
[NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
}
//遍历数据:
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]);
}
}
【四】使用系统自动创建的CoreData
系统帮我们在AppDelegate中创建了一个NSPersistentContainer实例,以及一个saveContext方法。(并且已经帮我们创建了.xcdatamodeld模型文件)
注意看saveContext,我们通过NSPersistentContainer的属性viewContext拿到NSManagedObjectContext对象,再通过save:方法进行数据的保存。
因为系统并没有帮我们适配旧系统,所以如果App要在非iOS10的旧系统运行,还需要做类似上面 “搭建上下文环境”的工作,因为那里的代码在iOS10以下和以上的代码中都可以执行。
如果是Xcode8之前的版本自动创建的Core Data Stack,会不一样(跟情况2类似),如下图:
一个大坑:
这里有个坑,在Xcode8中,Codegen下拉选择框中增加了Class/Definition这一选项,而且是默认的预设值,这时候系统会自动帮我们这个实体创建了NSManagedObject子类,我们不需要再创建实体类,最坑的是,这些自动创建的类,在导航面板是看不见的!!!然后你很容易再重复手动创建NSManagedObject子类,这时候就会报类似「duplicate symbol _OBJC_METACLASS_Photography in:...」这类错误。
所以,如果你想自己手动创建NSManagedObject子类,就要把系统预设的Class/Definition改为Manual/None。
使用系统自动创建的CoreData时,非常的方便,我们只需要在 xxxxx.xcdatamodeld 中添加好实体即可,然后就可以直接使用了。
#import "Man+CoreDataClass.h"
#import "AppDelegate.h"
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSPersistentContainer *container = delegate.persistentContainer;
Man *man = [NSEntityDescription insertNewObjectForEntityForName:@"Man" inManagedObjectContext:container.viewContext];
man.name = @"小明";
man.height = @"180";
// ++++++++ 保存数据 ++++++++
NSError *error = nil;
BOOL success = [container.viewContext save:&error];
if (!success) {
[NSException raise:@"访问数据库错误!" format:@"%@", [error localizedDescription]];
}else{
NSLog(@"访问数据库成功!");
}
// ++++++++ 查询数据 ++++++++
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//设置要查询的实体:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Man" inManagedObjectContext:container.viewContext];
request.entity = entity;
NSError *error1 = nil;
NSArray *objs = [container.viewContext executeFetchRequest:request error:&error];
if (error1) {
[NSException raise:@"查询错误" format:@"%@", [error1 localizedDescription]];
}
//遍历数据:
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]);
}
【五】关于CoreData的版本迁移
应用场景:修改了实体的数据结构(比如说某个实体增加了一个特性),因为APP版本更新后沙盒中的NSDocumentDirectory 中的缓存数据都不会被清除,这时候就要进行版本迁移了,否则已经安装旧App的手机,在更新应用后,两边数据结构不一致导致不能识别,会崩溃。
步骤:
- 选中.xcdatamodeld文件,Editor > Add Model Version,创建一个新版的.xcdatamodeld文件
- 切换到新版的.xcdatamodeld文件(切换成功后会有绿色的勾),如下图:
- 对.xcdatamodeld文件进行你想要的修改
- 创建NSPersistentStore的时候,options参数传一个dictionary,值如下:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
//在初始化的时候用到了版本迁移的设置
- (void)initializeCoreDataLessThaniOS10 {
// Get managed object model(拿到模型文件,也就是.xcdatamodeld文件(我们会在初始化完Core data Stack后创建))
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MoveBand" withExtension:@"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom != nil, @"Error initalizing Managed Object Model");
// Create persistent store coordinator(创建NSPersistentStoreCoordinator对象(需要传入上述创建的NSManagedObjectModel对象))
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
// Creat managed object context(创建NSManagedObjectContext对象(_context是声明在.h文件的属性——因为其他类也要用到这个属性))
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// assgin persistent store coordinator(赋值persistentStoreCoordinator)
_context.persistentStoreCoordinator = psc;
// Create .sqlite file(在沙盒中创建.sqlite文件)
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"DataModel.sqlite"];
// Create persistent store(异步创建NSPersistentStore并add到NSPersistentStoreCoordinator对象中,作用是设置保存的数据类型(NSSQLiteStoreType)、保存路径、是否支持版本迁移等)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 用于支持版本迁移的参数
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
NSPersistentStoreCoordinator *psc = _context.persistentStoreCoordinator;
// 备注,如果options参数传nil,表示不支持版本迁移
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error];
NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
});
}
最后值得注意的是:Core Data的延迟加载
Core Data不会根据实体中的关联关系立即获取相应的关联对象,比如通过Core Data取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会查询数据库,加载Card实体的信息。
【六】CoreData第三方库:MagicalRecord
CoreData是苹果自家推出的一个持久化框架,使用起来更加面向对象。但是在使用过程中会出现大量代码,
而且CoreData学习曲线比较陡峭,如果掌握不好,在使用过程中很容易造成其他问题。
国外开发者开源了一个基于CoreData封装的第三方——MagicalRecord,就像是FMDB封装SQLite一样,
MagicalRecord封装的CoreData,使得原生的CoreData更加容易使用。并且MagicalRecord降低了CoreData的使用门槛,
不用去手动管理之前的PSC、MOC等对象。
添加MagicalRecord到项目中
将MagicalRecord
添加到项目中,和使用其他第三方一样,可以通过下载源码和CocoaPods
两种方式添加。
但是不推荐直接拖源码到项目中,一是需要自己管理代码更新,另一个原因是,直接拖源码进项目是会报错的,修改起来很麻烦。
推荐通过CocoaPods
安装MagicalRecord
,需要在Podfile
中加入下面命令,后续只需要通过命令来更新。
pod "MagicalRecord"
很多操作在这份MagicalRecord中文文档中都说明的很清楚,这里作简单归纳总结
1、AppDelegate中的设置
#import <MagicalRecord/MagicalRecord.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecord setupCoreDataStack];
// ...
return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[MagicalRecord cleanUp];
}
2、对象的储存和查询
#import <MagicalRecord/MagicalRecord.h>
// 获取上下文环境
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
// 在当前上下文环境中创建一个新的 Person 对象.
Man *person = [Man MR_createEntityInContext:localContext];
person.name = @"MagicalRecord存储";
// 保存修改到当前上下文中.
[localContext MR_saveToPersistentStoreAndWait];
NSArray *peopleArray = [Man MR_findAll];
for (Man *man in peopleArray) {
NSLog(@"名称:%@",man.name);
}
对于MagicalRecord 的使用感受就是,确实如它的名称一样,如此简洁和方便的实现了对象的增删改查,如此的充满魔力,关于的它的更多使用可以参考上面的中文文档,相信大家都可以熟练掌握这个好用的类库!
另外还有其他的第三方存储库: 可以存对象的数据库realm-cocoa使用时参考这篇文章:移动端数据库新王者:realm
参考文章:
iOS 开发之 CoreData
CoreData的使用
iOS CoreData数据库之创建详解
「死磕」Core Data——入门
认识CoreData - 初识CoreData