CoreData的使用/以及coreData中的多线程问题/版本迁移(二)

来源:互联网 发布:数据分析师去哪里考试 编辑:程序博客网 时间:2024/06/11 21:51

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 部分.
file-list
file-list

这里, 实际上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对象。

QQ截图20160729183339.png

fetched Property

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

Fetch Requests

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

1469788494825461.png

Fetch Requests

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






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

{

    

// 创建请求对象,并指明操作Employee

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 设置排序规则,指明根据height字段升序排序

NSSortDescriptor *heightSort = [NSSortDescriptorsortDescriptorWithKey:@"age"ascending:YES];

request.sortDescriptors = @[heightSort];

// 创建NSFetchedResultsController控制器实例,并绑定MOC

NSError *error = nil;

NSFetchedResultsController  *fetchedResultController = [[NSFetchedResultsController                                           alloc] initWithFetchRequest:request                                                   managedObjectContext:context

                                        sectionNameKeyPath:@"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的执行流程是紧密相关的。

@protocol NSFetchedResultsSectionInfo

/* Name of the section */
@property (nonatomic, readonly) NSString *name;

/* Title of the section (used when displaying the index) */
@property (nullable, nonatomic, readonly) NSString *indexTitle;

/* Number of objects in section */
@property (nonatomic, readonly) NSUInteger numberOfObjects;

/* Returns the array of objects in the section. */
@property (nullable, nonatomic, readonly) NSArray *objects;

@end // NSFetchedResultsSectionInfo

//通过fetchedResultController中的sections(sections对应的是所有实体的所有对象)属性获取所有的section对象(一个section对应一个实体的所有对象,section也是一个数组)

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
   
 return fetchedResultController.sections.count;
}

// 通过当前section的下标从sections数组中取出对应的section对象,并从section对象中获取所有对象count

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
   
 return fetchedResultController.sections[section].numberOfObjects;
}

// FRC根据indexPath获取托管对象,并给cell赋值

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
    cell.textLabel.text = emp.name;
    return cell;

}

// 创建FRC对象时,通过sectionNameKeyPath:传递进去的section title的属性名,在这里获取对应的属性值

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
return fetchedResultController.sections[section].indexTitle;

}

// 是否可以编辑

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    
 YES;

}

// 这里是简单模拟UI删除cell后,本地持久化区数据和UI同步的操作。在调用下面MOC保存上下文方法后,FRC会回调代理方法并更新UI

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
     
if (editingStyle == UITableViewCellEditingStyleDelete) {
        // 删除托管对象
        Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
        [context deleteObject:emp];
        // 保存上下文环境,并做错误处理
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"tableView delete cell error : %@", error);
        }
    }
}

就像上面cellForRowAtIndexPath:方法中使用的一样,FRC提供了两个方法轻松转换indexPath和NSManagedObject的对象,在实际开发中这两个方法非常实用,这也是FRC和UITableView、UICollectionView深度融合的表现。

1
2
- (id)objectAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)indexPathForObject:(id)object;
// Cell数据源发生改变会回调此方法,例如添加新的托管对象等
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeUpdate: {
            UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
            cell.textLabel.text = emp.name;
        }
            break;
    }
}
// Section数据源发生改变回调此方法,例如修改section title等。
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        default:
            break;
    }
}
// 本地数据源发生改变,将要开始回调FRC代理方法。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [tableView beginUpdates];
}
// 本地数据源发生改变,FRC代理方法回调完成。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [tableView endUpdates];
}
// 返回section的title,可以在这里对title做进一步处理。这里修改title后,对应section的indexTitle属性会被更新。
- (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
    return [NSString stringWithFormat:@"sectionName %@", sectionName];
}

-(NSArray *)contactEntity

{

    if (_contactEntity ==nil) {

        _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/37ab8f336f76

CoreData中的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 -> 选择基于哪个版本的模型文件(一般都是选择目前最新的版本),新建模型文件完成。

对于新版本模型文件的命名,我在创建新版本模型文件时,一般会拿当前工程版本号当做后缀,这样在模型文件版本比较多的时候,就可以很容易将模型文件版本和工程版本对应起来。

QQ截图20160802163715.jpg

创建新版本模型文件

添加完成后,会发现之前的模型文件会变成一个文件夹,里面包含着多个模型文件。

270478-575b1d55f9894b3f.png

模型文件夹

在新建的模型文件中,里面的文件结构和之前的文件结构相同。后续的修改都应该在新的模型文件上,之前的模型文件不要再动了,在修改完模型文件后,记得更新对应的模型类文件。

基于新的模型文件,对Employee实体做如下修改,下面的版本迁移也以此为例。

1470127352473942.png

修改之前

添加一个String类型的属性,设置属性名为sectionName。

1470127367284982.png

修改之后

此时还应该选中模型文件,设置当前模型文件的版本。这里选择将最新版本设置为刚才新建的1.1.0版本,模型文件设置工作完成。

Show The File Inspector -> Model Version -> Current 设置为最新版本。

1470127392948891.png

设置版本

对模型文件的设置已经完成了,接下来系统还要知道我们想要怎样迁移数据。在迁移过程中可能会存在多种可能,苹果将这个灵活性留给了我们完成。剩下要做的就是编写迁移方案以及细节的代码。

轻量级版本迁移

轻量级版本迁移方案非常简单,大多数迁移工作都是由系统完成的,只需要告诉系统迁移方式即可。在持久化存储协调器(PSC)初始化对应的持久化存储(NSPersistentStore)对象时,设置options参数即可,参数是一个字典。PSC会根据传入的字典,自动推断版本迁移的过程。

字典中设置的key:

  • NSMigratePersistentStoresAutomaticallyOption设置为YES,CoreData会试着把低版本的持久化存储区迁移到最新版本的模型文件。

  • NSInferMappingModelAutomaticallyOption设置为YES,CoreData会试着以最为合理地方式自动推断出源模型文件的实体中,某个属性到底对应于目标模型文件实体中的哪一个属性。

版本迁移的设置是在创建MOC时给PSC设置的,为了使代码更直观,下面只给出发生变化部分的代码,其他MOC的初始化代码都不变。

1
2
3
4
5
// 设置版本迁移方案
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                                NSInferMappingModelAutomaticallyOption : @YES};
// 创建持久化存储协调器,并将迁移方案的字典当做参数传入
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:options error:nil];

修改实体名

假设需要对已存在实体进行改名操作,需要将重命名后的实体Renaming ID,设置为之前的实体名。下面是Employee实体进行操作。

1470127597754425.png

修改实体名

修改后再使用实体时,应该将实体名设为最新的实体名,这里也就是Employee2,而且数据库中的数据也会迁移到Employee2表中。

1
2
3
4
5
Employee2 *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee2" inManagedObjectContext:context];
emp.name = @"lxz";
emp.brithday = [NSDate date];
emp.height = @1.9;
[context save:nil];


版本迁移方法二:Mapping Model 迁移方案(有待研究)
0 0
原创粉丝点击