CoreData 的使用以及 CoreData 中的多线程问题
CoreData的使用
1.coreData简介
coreData是苹果对sqlite的封装,不用操作sqlite语句,他提供了对象关系映射功能,能将oc对象转化成数据,保存在sqlite中,也能将保存的数据还原成oc对象;
coredata有两种队列:私有队列,主队列
coreData中的主要包括这几个部分:管理对象上下文,数据持久化协调器,模型文件(包含实体,实体对应的是实体类),
2.coreData的使用
// 1.创建模型文件 [相当于一个数据库里的所有表]
// 2.添加实体(可以添加多个实体) ,添加相应实体的属性[添加一个实体相当于数据库中添加了一张表]
-
Undefined: 默认值,参与编译会报错
-
Integer 16: 整数,表示范围 -32768 ~ 32767
-
Integer 32: 整数,表示范围 -2147483648 ~ 2147483647
-
Integer 64: 整数,表示范围 –9223372036854775808 ~ 9223372036854775807
-
Float: 小数,通过MAXFLOAT宏定义来看,最大值用科学计数法表示是 0x1.fffffep+127f
-
Double: 小数,小数位比Float更精确,表示范围更大
-
String: 字符串,用NSString表示
-
Boolean: 布尔值,用NSNumber表示
-
Date: 时间,用NSDate表示
-
Binary Data: 二进制,用NSData表示
-
Transformable: OC对象,用id表示。可以在创建托管对象类文件后,手动改为对应的OC类名。使用的前提是,这个OC对象必须遵守并实现NSCoding协议
// 3.创建对应的实体类() [相当于管理对象模型,有一个实体就创建一个实体类,就相当于一个实体模型]
那么对于CoreData, 我们不用直接接触sql语句, 这种表间的联合查询我们应该怎么办呢?
CoreData 的联合查询.
1.我们创建一个部门
的示例, 请注意 Employee 的 Releationships 部分.
这里, 实际上Department做为Employee的外键, 在Employee中有一个字段为depart. 如此设置之后,这两张表已经完成前面我们描述的表间关联
, 不用出现join关键字, 我们已经将两张表牢牢的绑在一起了.
Department实体添加Relationships的操作和Employee都一样,区别在于用红圈标出的Type,这里设置的To Many一对多的关系。这里默认是To One一对一,上面的Employee就是一对一的关系。也就符合一个Department可以有多个Employee,而Employee只能有一个Department的情况,这也是符合常理的。
Relationships类似于SQLite的外键,定义了在同一个模型中,实体与实体之间的关系。可以定义为对一关系或对多关系,也可以定义单向或双向的关系,根据需求来确定。如果是对多的关系,默认是使用NSSet集合来存储模型。
Inverse是两个实体在Relationships中设置关联关系后,通过设置inverse为对应的实体,这样可以从一个实体找到另一个实体,使两个实体具有双向的关联关系。
Fetched Properties
在实体最下面,有一个Fetched Properties选项,这个选项用的不多,这里就不细讲了。
Fetched Properties用于定义查询操作,和NSFetchRequest功能相同。定义fetchedProperty对象后,可以通过NSManagedObjectModel类的fetchRequestFromTemplateWithName:substitutionVariables:方法或其他相关方法获取这个fetchedProperty对象。
fetched Property
获取这个对象后,系统会默认将这个对象缓存到一个字典中,缓存之后也可以通过fetchedProperty字典获取fetchedProperty对象。
Fetch Requests
在模型文件中Entities下面有一个Fetch Requests,这个也是配置请求对象的。但是这个使用起来更加直观,可以很容易的完成一些简单的请求配置。相对于上面讲到的Fetched Properties,这个还是更方便使用一些。
Fetch Requests
上面是对Employee实体的height属性配置的Fetch Request,这里配置的height要小于2米。配置之后可以通过NSManagedObjectModel类的fetchRequestTemplateForName:方法获取这个请求对象,参数是这个请求配置的名称,也就是EmployeeFR。
// 4.生成管理对象上下文 并关联模型文件生成数据库
/*
* 关联的时候,如果本地没有数据库文件,Coreadata自己会创建
*/
// 创建上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
// model模型文件
// NSManagedObjectModel *model =[NSManagedObjectModel mergedModelFromBundles:nil];//使用这个方法,如果 bundles为nil会把bundles里面的所有模型文件的表放在一个数据库中
//使用下面这个方法,是把一个模型文件对应一个数据库
NSURL *companyURL = [[NSBundle mainBundle]URLForResource:modelName withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc]initWithContentsOfURL:companyURL];
// 持久化存储调度器(通过她把模型链接到本地数据库)
// 持久化,把数据保存到一个文件,而不是内存
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 告诉Coredata数据库的名字和路径
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlitePath = [doc stringByAppendingPathComponent:@"company.sqlite"];
NSLog(@"%@",sqlitePath);
//添加持久化存储(也就时设置存储类型和存储路径)
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURLfileURLWithPath:sqlitePath] options:nil error:nil];
//设置上下文的持久化调度器
context.persistentStoreCoordinator = store;
_context = context;
}
==========
//使用这个方法是把不同的模型文件的表放到不同的数据库中
-(NSManagedObjectContext *)setupContextWithModelName:(NSString*)modelName{
// 1.创建模型文件[相当于一个数据库里的表]
// 2.添加实体[一张表]
// 3.创建实体类 [相当模型]
// 4.生成上下文关联模型文件生成数据库
/*
* 关联的时候,如果本地没有数据库文件,Coreadata自己会创建
*/
// 上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
// 上下文关连数据库
// model模型文件
NSURL *companyURL = [[NSBundlemainBundle]URLForResource:modelNamewithExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModelalloc]initWithContentsOfURL:companyURL];
// 持久化存储调度器
// 持久化,把数据保存到一个文件,而不是内存
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinatoralloc]initWithManagedObjectModel:model];
// 告诉Coredata数据库的名字和路径
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
NSString *sqliteName = [NSStringstringWithFormat:@"%@.sqlite",modelName];
NSString *sqlitePath = [doc stringByAppendingPathComponent:sqliteName];
NSLog(@"%@",sqlitePath);
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURLfileURLWithPath:sqlitePath] options:nil error:nil];
context.persistentStoreCoordinator = store;
return context;
}
==========
// 数据库的操作 CURD Create Update Read Delete
#pragma mark -----增
// 通过模型文件中的实体创建一个员工对象
//Employee *emp = [[Employee alloc] init];
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee"inManagedObjectContext:_context];
emp.name = @"wangwu";
emp.height = @1.80;
emp.birthday = [NSDate date];
// 直接保存数据库
NSError *error = nil;
[_context save:&error];
if (error) {
NSLog(@"%@",error);
}
#pragma mark ----查
// 1.FectchRequest 创建抓取请求对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 2.设置过滤条件
// 查找zhangsan
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@",
@"zhangsan"];
request.predicate = pre;
// 3.设置排序
// 身高的升序排序
NSSortDescriptor *heigtSort = [NSSortDescriptor sortDescriptorWithKey:@"height"ascending:NO];
request.sortDescriptors = @[heigtSort];
// 4.执行请求
NSError *error = nil;
NSArray *emps = [_context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"error");
}
//NSLog(@"%@",emps);
//遍历员工
for (Employee *emp in emps) {
NSLog(@"名字 %@ 身高 %@ 生日 %@",emp.name,emp.height,emp.birthday);
}
#pragma mark -----改
// 1.查找到zhangsan
// 1.1FectchRequest 抓取请求对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 1.2设置过滤条件
// 查找zhangsan
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@",
@"zhangsan"];
request.predicate = pre;
// 1.3执行请求
NSArray *emps = [_context executeFetchRequest:request error:nil];
// 2.更新身高
for (Employee *e in emps) {
e.height = @2.0;
}
// 3.保存
[_context save:nil];
#pragma mark ------删
// 1.查找lisi
// 1.1FectchRequest 抓取请求对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 1.2设置过滤条件
// 查找zhangsan
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@",
@"lisi"];
request.predicate = pre;
// 1.3执行请求
NSArray *emps = [_context executeFetchRequest:request error:nil];
// 2.删除
for (Employee *e in emps) {
[_context deleteObject:e];
}
// 3.保存
[_context save:nil];
====================================
// 1.FectchRequest 抓取请求对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
//设置身高的升序排序
NSSortDescriptor *heigtSort = [NSSortDescriptor sortDescriptorWithKey:@"height"ascending:NO];
request.sortDescriptors = @[heigtSort];
// 模糊查询
// 名字以"wang"开头
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@",@"wangwu1"];
// request.predicate = pre;
// 名字以"1"结尾
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name ENDSWITH %@",@"1"];
// request.predicate = pre;
// 名字包含"wu1"
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name CONTAINS %@",@"wu1"];
// request.predicate = pre;
// like
//以wangwu1*结尾
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like %@",@"*wu12"];
//以wangwu1开头
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like %@",@"wu12*"];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like %@",@"正则表达是语句"];
request.predicate = pre;
// 4.执行请求
NSError *error = nil;
NSArray *emps = [_context executeFetchRequest:request error:&error];
====================================
分页查询
// 1.FectchRequest 抓取请求对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 设置身高的升序排序
NSSortDescriptor *heigtSort = [NSSortDescriptorsortDescriptorWithKey:@"height"ascending:NO];
request.sortDescriptors = @[heigtSort];
// 总有共有15数据
// 每次获取6条数据
// 第一页 0,6
// 第二页 6,6
// 第三页 12,6 3条数据
// 分页查询 limit 0,5
// 分页的起始索引
request.fetchOffset = 12;
// 分页的条数
request.fetchLimit = 6;
// 4.执行请求
NSError *error = nil;
NSArray *emps = [_context executeFetchRequest:requesterror:&error];//执行查询
++++++++++++++++++++++++++++
直接创建查询结果控制器查询
//懒加载
-(NSFetchedResultsController *)fetchController
{
if (_fetchController ==nil) {
//查询请求
NSFetchRequest *fetchrequest = [[NSFetchRequestalloc]init];
//1.获取实体描述
fetchrequest.entity = [NSEntityDescriptionentityForName:@"XMPPUserCoreDataStorageObject"inManagedObjectContext:[XMPPRosterCoreDataStoragesharedInstance].mainThreadManagedObjectContext];
//获取实体描述
//2.谓词
NSPredicate *pre = [NSPredicatepredicateWithFormat:@"subscription = %@",@"both"];
[fetchrequest setPredicate:pre];
//3.排序
NSSortDescriptor *sort = [NSSortDescriptorsortDescriptorWithKey:@"jidStr"ascending:YES];
fetchrequest.sortDescriptors = @[sort];
//创建对象查询控制器对象
_fetchController = [[NSFetchedResultsControlleralloc]initWithFetchRequest:fetchrequestmanagedObjectContext:[XMPPRosterCoreDataStoragesharedInstance].mainThreadManagedObjectContextsectionNameKeyPath:nilcacheName:@"contacts"];
//代理
_fetchController.delegate =self;
}
return_fetchController;
}
-(NSArray *)contactEntity
{
if (_contactEntity ==nil) {
_contactEntity = [NSArrayarray];
}
return_contactEntity;
}
- (void)viewDidLoad {
[superviewDidLoad];
// 查询数据
[self.fetchControllerperformFetch:nil];//执行查询获取请求
self.contactEntity = self.fetchController.fetchedObjects;
}
//查询控制器请求代理方法
-(void)controllerDidChangeContent:(NSFetchedResultsController*)controller
{
//重新获取数据
self.contactEntity =self.fetchController.fetchedObjects;
//刷新
[self.tableViewreloadData];
}
===================
//对数组的排序
NSSortDescriptor *heigtSort = [NSSortDescriptorsortDescriptorWithKey:@"height"ascending:NO];
// NSArray *resultArr=[arr sortedArrayUsingDescriptor:@[height]];
==============================================
CoreData中的多线程问题
1.一种比较好的iOS模式就是使用一个NSPersistentStoreCoordinator,以及两个独立的Contexts,一个context负责主线程与UI协作,一个context在后台负责耗时的处理,用Notifications的方式通知主线程的NSManagedObjectContext进行mergeChangesFromContextDidSaveNotification操作
2.后台线程做读写更新,而主线程只读
3.CoreData中的NSManagedObjectContext在多线程中不安全,如果想要多线程访问CoreData的话,最好的方法是一个线程一个NSManagedObjectContext,每个NSManagedObjectContext对象实例都可以使用同一个NSPersistentStoreCoordinator实例,这个实例可以很安全的顺序访_问永久存储,这是因为NSManagedObjectContext会在便用NSPersistentStoreCoordinator前上锁。ios5.0为NSManagedObjectContext提供了initWithConcurrentcyType方法,其中的一个NSPrivateQueueConcurrencyType,会自动的创建一个新线程来存放NSManagedObjectContext而且它还会自动创建NSPersistentStoreCoordinator,
CoreData 多线程下NSManagedObjectContext的使用,nsmanagedobject
在Google的时候,我发现了这样两篇老外的博客(,)。前者是介绍NSManagedObjectContext在多线程下的三种设计,后者是博主对这三种设计进行的性能测试。下面我将一一介绍:1. persistentStoreCoordinator<-mainContext<-privateContext 这种设计就是我之前在项目中使用的,也是阻塞UI线程最严重的一种设计。它总共有两个Context,一个是UI线程中使用的mainContext,一个是子线程中使用的privateContext,他们的关系是privateContext.parentContext = mainContext,而mainContext是与Disk连接的Context,所以这种设计下,每当子线程privateContext进行save操作以后,它会将数据库所有变动Push up到其父Context,也就是mainContext中去,注意:这时子线程的save操作并没有任何关于Disk IO的操作。而后mainContext在UI线程又要执行一次save操作才能真正将数据变动写进数据库中,这里的save操作就与Disk IO有关了,而且又是在主线程,所以说这种设计是最阻碍UI线程的。 2. persistentStoreCoordinator<-backgroundContext<-mainContext<-privateContext 这种设计是第一种的改进设计,也是上述的老外博主推荐的一种设计方式。它总共有三个Context,一是连接persistentStoreCoordinator也是最底层的backgroundContext,二是UI线程的mainContext,三是子线程的privateContext,后两个Context在1中已经介绍过了,这里就不再具体介绍,他们的关系是privateContext.parentContext = mainContext, mainContext.parentContext = backgroundContext。下面说说它的具体工作流程。 在应用中,如果我们有API操作,首先我们会起一个子线程进行API请求,在得到Response后要进行数据库操作,这是我们要创建一个privateContext进行数据的增删改查,然后call privateContext的save方法进行存储,这里的save操作只是将所有数据变动Push up到它的父Context中也就是mainContext中,然后mainContext继续call save方法,将数据变动Push up到它的父Context中也就是backgroundContext,最后调用backgroundContext的save方法真正将数据变动存储到Disk数据库中,在这个过程中,前两个save操作相对耗时较少,真正耗时的操作是最后backgroundContext的save操作,因为只有它有Disk IO的操作。 3. persistentStoreCoordinator<-mainContext persistentStoreCoordinator<-privateContext 第三种设计是最直观的一种设计,无论是mainContext还是privateContext都是连接persistentStoreCoordinator的。这种设计的工作流程是: 首先在ViewController中要添加一个名为NSManagedObjectContextDidSaveNotification的通知 ,然后子线程中创建privateContext,进行数据增删改查操作,直接save到本地数据库,这时在ViewController中会回调之前注册的NSManagedObjectContextDidSaveNotification的回调方法,在该方法中调用mainContext的mergeChangesFromContextDidSaveNotification:notification方法,将所有的数据变动merge到mainContext中,这样就保持了两个Context中的数据同步。由于大部分的操作都是privateContext在子线程中操作的,所以这种设计是UI线程耗时最少的一种设计,但是它的代价是需要多写mergeChanges的方法。(注:前两种parent,child的Context,一旦childContext调用save方法,其parentContext不用任何merge操作,CoreData自动将数据merge到parentContext当中) 总结: 第一种设计是失败的设计,完全可以不考虑,第二种设计比较复杂繁琐,但是它是最方便而且UI线程的阻塞时间也是相对较少的一种。第三种设计是最少阻塞UI的一种,但是这种设计操作比较繁琐,应用场合是数据量比较大的应用,一般会应用在企业应用中,所以如果你不是企业级的应用或者不是数据量很大的应用,我还是推荐第二种设计。
通常主线程context使用NSMainQueueConcurrencyType,其他线程childContext使用NSPrivateQueueConcurrencyType. child和parent的特点是要用Block进行操作,performBlock,或者performBlockAndWait,保证线程安全。这两个函数的区别是performBlock不会阻塞运行的线程,相当于异步操作,performBlockAndWait会阻塞运行线程,相当于同步操作。
-(
void
)main{
02.
self.privateContext
= [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
03.
[self.privateContext
setPersistentStoreCoordinator:self.mainContext.persistentStoreCoordinator];
04.
05.
[[NSNotificationCenter
defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
06.
if
(note.object
== self.privateContext) {
07.
dispatch_async(dispatch_get_main_queue(),
^{//回到主线程更新
08.
[self.mainContext
performBlock:^{
09.
[self.mainContext
mergeChangesFromContextDidSaveNotification:note];
10.
}];
11.
});
12.
}
13.
}];
14.
15.
//执行耗时的操作
16.
17.
//执行完毕
18.
[self.privateContext
performBlock:^{
19.
NSError
* error = nil;
20.
if
([self.privateContext
hasChanges]) {
21.
[self.privateContext
save:&error];
22.
}
23.
}];
24.
}</