CoreData 的使用以及 CoreData 中的多线程问题


CoreData 的使用以及 CoreData 中的多线程问题

CoreData的使用

1.coreData简介

      coreData是苹果对sqlite的封装,不用操作sqlite语句,他提供了对象关系映射功能,能将oc对象转化成数据,保存在sqlite中,也能将保存的数据还原成oc对象;

      coredata有两种队列:私有队列,主队列

       coreData中的主要包括这几个部分:管理对象上下文,数据持久化协调器,模型文件(包含实体,实体对应的是实体类),



2.coreData的使用


//    1.创建模型文件 [相当于一个数据库里的所有表]

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

//    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协议



CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

//    3.创建对应的实体类() [相当于管理对象模型,有一个实体就创建一个实体类,就相当于一个实体模型]

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题

CoreData 的使用以及 CoreData 中的多线程问题CoreData 的使用以及 CoreData 中的多线程问题


那么对于CoreData, 我们不用直接接触sql语句, 这种表间的联合查询我们应该怎么办呢?

CoreData 的联合查询.

1.我们创建一个部门的示例, 请注意 Employee 的 Releationships 部分.
CoreData 的使用以及 CoreData 中的多线程问题
CoreData 的使用以及 CoreData 中的多线程问题

这里, 实际上Department做为Employee的外键, 在Employee中有一个字段为depart. 如此设置之后,这两张表已经完成前面我们描述的表间关联, 不用出现join关键字, 我们已经将两张表牢牢的绑在一起了.


Department实体添加Relationships的操作和Employee都一样,区别在于用红圈标出的Type,这里设置的To Many一对多的关系。这里默认是To One一对一,上面的Employee就是一对一的关系。也就符合一个Department可以有多个Employee,而Employee只能有一个Department的情况,这也是符合常理的。

CoreData 的使用以及 CoreData 中的多线程问题

Relationships类似于SQLite的外键,定义了在同一个模型中,实体与实体之间的关系。可以定义为对一关系或对多关系,也可以定义单向或双向的关系,根据需求来确定。如果是对多的关系,默认是使用NSSet集合来存储模型。

Inverse是两个实体在Relationships中设置关联关系后,通过设置inverse为对应的实体,这样可以从一个实体找到另一个实体,使两个实体具有双向的关联关系。

Fetched Properties

在实体最下面,有一个Fetched Properties选项,这个选项用的不多,这里就不细讲了。

Fetched Properties用于定义查询操作,和NSFetchRequest功能相同。定义fetchedProperty对象后,可以通过NSManagedObjectModel类的fetchRequestFromTemplateWithName:substitutionVariables:方法或其他相关方法获取这个fetchedProperty对象。

CoreData 的使用以及 CoreData 中的多线程问题

fetched Property

获取这个对象后,系统会默认将这个对象缓存到一个字典中,缓存之后也可以通过fetchedProperty字典获取fetchedProperty对象。

Fetch Requests

在模型文件中Entities下面有一个Fetch Requests,这个也是配置请求对象的。但是这个使用起来更加直观,可以很容易的完成一些简单的请求配置。相对于上面讲到的Fetched Properties,这个还是更方便使用一些。

CoreData 的使用以及 CoreData 中的多线程问题

Fetch Requests

上面是对Employee实体的height属性配置的Fetch Request,这里配置的height要小于2米。配置之后可以通过NSManagedObjectModel类的fetchRequestTemplateForName:方法获取这个请求对象,参数是这个请求配置的名称,也就是EmployeeFR。


CoreData 的使用以及 CoreData 中的多线程问题



//    4.生成管理对象上下文 并关联模型文件生成数据库

    /*

     * 关联的时候,如果本地没有数据库文件,Coreadata自己会创建

     */

    // 创建上下文

    NSManagedObjectContext *context = [[NSManagedObjectContext allocinit];

    // model模型文件

  // NSManagedObjectModel *model =[NSManagedObjectModel mergedModelFromBundles:nil];//使用这个方法,如果 bundlesnil会把bundles里面的所有模型文件的表放在一个数据库中

  //使用下面这个方法,是把一个模型文件对应一个数据库

    NSURL *companyURL = [[NSBundle mainBundle]URLForResource:modelName  withExtension:@"momd"];

    NSManagedObjectModel *model = [[NSManagedObjectModel alloc]initWithContentsOfURL:companyURL];

  



    // 持久化存储调度器(通过她把模型链接到本地数据库)

    // 持久化,把数据保存到一个文件,而不是内存

    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator allocinitWithManagedObjectModel:model];

    

    // 告诉Coredata数据库的名字和路径

    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMaskYESlastObject];

    

    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];

        //获取实体描述

<code class="hljs vhdl has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"> NSEntityDescription *<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">entity</span> = [NSEntityDescription entityForName:TableName inManagedObjectContext:<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">context</span>];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"></ul>

        //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与多线程
为了在查询数据的时候不让界面停滞,使用多线程是不可避免,一般我们会用thread,串行线程或者并发线程。
coredata与多线程交互的时候,每个线程都必须拥有一个manager context对象,一般有两种方式:
1.每一个线程使用私有的manager context,共享一个 persistent store coordinator
2.每个线程使用私有的manager context和私有的persistent store coordinator
对于这两种方式,我们比较推荐使用第一钟方式,因为使用第二种方式的会消耗我们更多的内存,所以推荐使用第一种。

CoreData里面还带有一个通知NSManagedObjectContextDidSaveNotification,主要监听NSManagedObjectContext的数据是否改变,并合并数据改变到相应context


=============================
另一种解释:

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.}</