CoreData的使用/以及coreData中的多线程问题/版本迁移(二)
2017-03-23 09:11
661 查看
1.coreData简介
coreData是苹果对sqlite的封装,不用操作sqlite语句,他提供了对象关系映射功能,能将oc对象转化成数据,保存在sqlite中,也能将保存的数据还原成oc对象;
coredata有两种队列:私有队列,主队列
coreData中的主要包括这几个部分:管理对象上下文,数据持久化协调器,模型文件(包含实体,实体对应的是实体类),2.coreData的使用// 1.创建模型文件 [相当于一个数据库里的所有表]
那么对于CoreData, 我们不用直接接触sql语句, 这种表间的联合查询我们应该怎么办呢?
CoreData 的联合查询.1.我们创建一个
部门的示例, 请注意 Employee 的 Releationships 部分.
![](http://ac-4fcdbq4a.clouddn.com/0b2ba3924d300a57.png)
![](http://ac-4fcdbq4a.clouddn.com/a584ef16863c1645.png)
表间关联, 不用出现join关键字, 我们已经将两张表牢牢的绑在一起了.Department实体添加Relationships的操作和Employee都一样,区别在于用红圈标出的Type,这里设置的To Many一对多的关系。这里默认是To One一对一,上面的Employee就是一对一的关系。也就符合一个Department可以有多个Employee,而Employee只能有一个Department的情况,这也是符合常理的。
![](http://cc.cocimg.com/api/uploads/20160729/1469788427835621.png)
![](http://cc.cocimg.com/api/uploads/20160729/1469788494825461.png)
= %@", @"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{// 创建请求对象,并指明操作Employee表NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];// 设置排序规则,指明根据height字段升序排序NSSortDescriptor *heightSort = [NSSortDescriptorsortDescriptorWithKey:@"age"ascending:YES];request.sortDescriptors = @[heightSort];// 创建NSFetchedResultsController控制器实例,并绑定MOCNSError *error = nil;NSFetchedResultsController *fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:contextsectionNameKeyPath:@"name"cacheName:nil];// 设置代理,并遵守协议fetchedResultController.delegate = self;// 执行获取请求,执行后FRC会从持久化存储区加载数据,其他地方可以通过FRC获取数据[fetchedResultController performFetch:&error];// 错误处理if (error) {NSLog(@"NSFetchedResultsController init error : %@", error);}return fetchedResultController;}在上面初始化fetchedResultController时,传入的sectionNameKeyPath:参数,是指明当前托管对象的哪个属性当做section的title,在本文中就是Employee表的sectionName字段为section的title。从NSFetchedResultsSectionInfo协议的indexTitle属性获取这个值。在sectionNameKeyPath:设置属性名后,就以这个属性名作为分组title,相同的title会被分到一个section中。初始化fetchedResultController时参数managedObjectContext:传入了一个MOC参数,fetchedResultController只能监测这个传入的MOC发生的本地持久化改变。就像上面介绍时说的,其他MOC对同一个持久化存储区发生的改变,FRC则不能监测到这个变化。再往后面看到cacheName:参数,这个参数我设置的是nil。参数的作用是开启fetchedResultController的缓存,对获取的数据进行缓存并指定一个名字。可以通过调用deleteCacheWithName:方法手动删除缓存。但是这个缓存并没有必要,缓存是根据NSFetchRequest对象来匹配的,如果当前获取的数据和之前缓存的相匹配则直接拿来用,但是在获取数据时每次获取的数据都可能不同,缓存不能被命中则很难派上用场,而且缓存还占用着内存资源。在fetchedResultController初始化完成后,调用performFetch:方法来同步获取持久化存储区数据,调用此方法后FRC保存数据的属性才会有值。获取到数据后,调用tableView的reloadData方法,会回调tableView的代理方法,可以在tableView的代理方法中获取到FRC的数据。调用performFetch:方法第一次获取到数据并不会回调FRC代理方法。代理方法fetchedResultController中包含UITableView执行过程中需要的相关数据,可以通过fetchedResultController的sections属性,获取一个遵守协议(NSFetchedResultsSectionInfo)的对象数组,数组中的对象就代表一个section。在这个协议中有如下定义,可以看出这些属性和UITableView的执行流程是紧密相关的。
//通过fetchedResultController中的sections(sections对应的是所有实体的所有对象)属性获取所有的section对象(一个section对应一个实体的所有对象,section也是一个数组) |
{_contactEntity = [NSArrayarray];}return_contactEntity;}- (void)viewDidLoad {[superviewDidLoad];// 查询数据[self.fetchController performFetch:nil];//执行查询获取请求self.contactEntity = self.fetchController.fetchedObjects;}//查询控制器请求代理方法//可以通过上面的NSFetchedResultsSectionInfo协议中的sections取查询后的数据,也可以通过NSFetchedResultsController的fetchedObjects属性取数据(fetchedObjects存的是所有实体对象)-(void)controllerDidChangeContent:(NSFetchedResultsController*)controller{//重新获取数据self.contactEntity =self.fetchController.fetchedObjects;//self.contactEntity=controller.fetchedObjects;//刷新[self.tableViewreloadData];}+++++++++++++++++++++++++++++===================
//对数组的排序NSSortDescriptor *heigtSort = [NSSortDescriptorsort DescriptorWithKey:@"height" ascending:NO];// NSArray *resultArr=[arr sortedArrayUsingDescriptor:@[heigtSort]];==============================================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与多线程
为了在查询数据的时候不让界面停滞,使用多线程是不可避免,一般我们会用thread,串行线程或者并发线程。
coredata与多线程交互的时候,每个线程都必须拥有一个manager context对象,一般有两种方式:
1.每一个线程使用私有的manager context,共享一个 persistent store coordinator
2.每个线程使用私有的manager context和私有的persistent store coordinator
对于这两种方式,我们比较推荐使用第一钟方式,因为使用第二种方式的会消耗我们更多的内存,所以推荐使用第一种。CoreData里面还带有一个通知NSManagedObjectContextDidSaveNotification,主要监听NSManagedObjectContext的数据是否改变,并合并数据改变到相应context=============================
另一种解释:
CoreData 多线程下NSManagedObjectContext的使用,nsmanagedobject
介绍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];//保存之后就会发送名为NSManagedObjectContextDidSaveNotification的通知;
22.
}
23.
}];
24.
}</********实例二
参考:http://www.jianshu.com/p/37ab8f336f76CoreData中的NSManagedObjectContext在多线程中不安全,如果想要多线程访问CoreData的话,最好的方法是一个线程一个NSManagedObjectContext,每个NSManagedObjectContext对象实例都可以使用同一个
NSPersistentStoreCoordinator实例,这个实例可以很安全的顺序访问永久存储,这是因为 NSManagedObjectContext会在使用NSPersistentStoreCoordinator前上锁。初始化一个子线程中的管理上下文:NSManagedObjectContext *pravite = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];[pravite setParentContext:self.moc];//注册通知[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification
object:nil];[pravite performBlock:^{//自定开辟子线程NSLog(@"%@",[NSThread currentThread]);//执行操作//同步保存NSError *error = nil;if ([pravite hasChanges] && ![pravite save:&error]) {NSLog(@"%@",error);abort();//异常终止一个进程}}];子线程管理上下文在多线程中执行任务,管理数据,内容发生变化,触发通知,执行通知方法-(void)mocDidSaveNotification:(NSNotification *)notification{NSManagedObjectContext *saveContext = [notification object];//主线程中的上下文不用保存if (self.moc == saveContext) {return;}//使用一个持久化指针if (self.moc.persistentStoreCoordinator!=saveContext.persistentStoreCoordinator) {return;}//在主线程执行合并操作dispatch_async(dispatch_get_main_queue(), ^{[self.moc mergeChangesFromContextDidSaveNotification:notification];<br>
if([self.moc hasChanges])<br>
[self.moc save:nil];});}这样将子线程中的数据合并到主线程的管理上下文中.
===================
coredata中的版本迁移:(以下是转自别人的文章)创建新版本模型文件本文中讲的几种版本迁移方案,在迁移之前都需要对原有的模型文件创建新版本。选中需要做迁移的模型文件 -> 点击菜单栏Editor -> Add Model Version -> 选择基于哪个版本的模型文件(一般都是选择目前最新的版本),新建模型文件完成。对于新版本模型文件的命名,我在创建新版本模型文件时,一般会拿当前工程版本号当做后缀,这样在模型文件版本比较多的时候,就可以很容易将模型文件版本和工程版本对应起来。
![](http://cc.cocimg.com/api/uploads/20160802/1470127303213326.jpg)
![](http://cc.cocimg.com/api/uploads/20160802/1470127319701139.png)
![](http://cc.cocimg.com/api/uploads/20160802/1470127352473942.png)
![](http://cc.cocimg.com/api/uploads/20160802/1470127367284982.png)
![](http://cc.cocimg.com/api/uploads/20160802/1470127392948891.png)
![](http://cc.cocimg.com/api/uploads/20160802/1470127597754425.png)
版本迁移方法二:Mapping Model 迁移方案(有待研究)
相关文章推荐
- CoreData的使用以及coreData中的多线程问题
- coreData的原理和使用以及coreData中的多线程问题(二)
- 使用coredata导致的版本更新后程序crash的问题
- CoreData 的使用以及 CoreData 中的多线程问题
- 使用coredata导致的版本更新后程序crash的问题
- Android Studio 版本迁移的配置问题以及 GreenDAO3.0的配置使用
- 使用coredata导致的版本更新后程序crash的问题
- 使用coredata导致的版本更新后程序crash的问题
- 关于CoreData版本迁移的问题
- jedis,spring-redis-data 整合使用,版本问题异常以及解决。
- 使用coredata导致的版本更新后程序crash的问题
- [转]使用Filters滤镜弥补CSS3的跨浏览器问题以及兼容低版本IE
- IBM Data Studio使用----对象创建的双引号/大小写问题以及表/列重命名
- 使用C#通过Oracle.DataAccess连接Oracle,部署时需要注意版本问题
- CoreData的DataModel的版本控制和迁移(仅介绍轻量迁移)
- CoreData 多线程下NSManagedObjectContext的使用
- 在多线程环境中使用CoreData,以及一个简单的封装
- 关于CoreData和SQLite多线程访问时的线程安全问题
- coredata 进阶篇——IOS之分析网易新闻存储数据(CoreData的使用,增删改查)
- System.Data.SQLite.dll不能编译成AnyCPU问题的解决方案,以及它跨x86和x64的使用方法。