coreData详解

一、初识CoreData

CoreData的结构构成:ios

NSManagedObjectModel的构成:git

  能够经过Entity建立继承自NSManagedObject类的文件,这个文件就是开发中使用的托管对象,具有模型对象的表示功能,CoreData的本地持久化都是经过这个类及其子类完成的。github

  在CoreData的总体结构中,主要分为两部分。一个是NSManagedObjectContext管理的模型部分,管理着全部CoreData的托管对象。一个是SQLite实现的本地持久化部分,负责和SQL数据库进行数据交互,主要由NSPersistentStore类操做。这就构成了CoreData的大致结构。正则表达式


  从图中能够看出,这两部分都是比较独立的,两部分的交互由一个持久化存储调度器(NSPersistentStoreCoordinator)来控制。上层NSManagedObjectContext存储的数据都是交给持久化调度器,由调度器调用具体的持久化存储对象(NSPersistentStore)来操做对应的数据库文件,NSPersistentStore负责存储的实现细节。这样就很好的将两部分实现了分离。算法

 

二、认识CoreData-基础使用

  在模型文件的实体中,参数类型和平时建立继承自NSObject的模型类大致相似,可是仍是有一些关于类型的说明,下面简单的列举了一下。sql

  • Undefined: 默认值,参与编译会报错数据库

  • Integer 16: 整数,表示范围 -32768 ~ 32767express

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

 

  在实体最下面,有一个Fetched Properties选项,这个选项用的很少,这里就不细讲了。Fetched Properties用于定义查询操做,和NSFetchRequest功能相同。定义fetchedProperty对象后,能够经过NSManagedObjectModel类的fetchRequestFromTemplateWithName:substitutionVariables:方法或其余相关方法获取这个fetchedProperty对象。

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

属性设置:

  • default Value: 设置默认值,除了二进制不能设置,其余类型几乎都能设置。

  • optional: 在使用时是否可选,也能够理解为若是设置为NO,只要向MOC进行save操做,这个属性是否必须有值。不然MOC进行操做时会失败并返回一个error,该选项默认为YES

  • transient: 设置当前属性是否只存在于内存,不被持久化到本地,若是设置为YES,这个属性就不参与持久化操做,属性的其余操做没有区别。transient很是适合存储一些在内存中缓存的数据,例如存储临时数据,这些数据每次都是不一样的,并且不须要进行本地持久化,因此能够声明为transient的属性。

  • indexed: 设置当前属性是不是索引。添加索引后能够有效的提高检索操做的速度。可是对于删除这样的操做,删除索引后其余地方还须要作出相应的变化,因此速度会比较慢。

  • Validation: 经过Validation能够设置Max ValueMin Value,经过这两个条件来约定数据,对数据的存储进行一个验证。数值类型都有相同的约定方式,而字符串则是约定长度,date是约定时间。

  • Reg. Ex.(Regular Expression): 能够设置正则表达式,用来验证和控制数据,不对数据自身产生影响。(只能应用于String类型)

  • Allows External Storage: 当存储二进制文件时,若是遇到比较大的文件,是否存储在存储区以外。若是选择YES,存储文件大小超过1MB的文件,都会存储在存储区以外。不然大型文件存储在存储区内,会形成SQLite进行表操做时,效率受到影响。

Relationships设置:

  • delete rule: 定义关联属性的删除规则。在当前对象和其余对象有关联关系时,当前对象被删除后与之关联对象的反应。这个参数有四个枚举值,代码对应着模型文件的相同选项。

    NSNoActionDeleteRule 删除后没有任何操做,也不会将关联对象的关联属性指向nil。删除后使用关联对象的关联属性,可能会致使其余问题。

    NSNullifyDeleteRule 删除后会将关联对象的关联属性指向nil,这是默认值。

    NSCascadeDeleteRule 删除当前对象后,会将与之关联的对象也一并删除。

    NSDenyDeleteRule 在删除当前对象时,若是当前对象还指向其余关联对象,则当前对象不能被删除。

  • Type: 主要有两种类型,To OneTo Many,表示当前关系是一对多仍是一对一。

实体:

  • Parent Entity: 能够在实体中建立继承关系,在一个实体的菜单栏中经过Parent Entity能够设置父实体,这样就存在了实体的继承关系,最后建立出来的托管模型类也是具备继承关系的。注意继承关系中属性名不要相同。

  使用了这样的继承关系后,系统会将子类继承父类的数据,存在父类的表中,全部继承自同一父类的子类都会将父类部分存放在父类的表中。这样可能会致使父类的表中数据量过多,形成性能问题。


二、CoreData-基础使用

Fetched Properties

  在实体最下面,有一个Fetched Properties选项,这个选项用的很少,这里就不细讲了。Fetched Properties用于定义查询操做,和NSFetchRequest功能相同。定义fetchedProperty对象后,能够经过NSManagedObjectModel类的fetchRequestFromTemplateWithName:substitutionVariables:方法或其余相关方法获取这个fetchedProperty对象。

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

Fetch Requests

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



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

CoreData增删改查

- (IBAction)SchoolAdd:(UIButton *)sender {
    // 建立托管对象,并指明建立的托管对象所属实体名
    _student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext];
    _student.name = @"lxz";
    // 实体中全部基础数据类型,建立类文件后默认都是NSNumber类型的
    _student.age = @(23);
    
    // 经过上下文保存对象,并在保存前判断是否有更改
    NSError * error = nil;
    if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) {
        BOOL isAddSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
        if (isAddSuccess) {
            NSLog(@"SchoolisAddSuccess");
        }
    }
    
    // 错误处理,能够在这实现本身的错误处理逻辑
    if (error) {
        NSLog(@"CoreData Insert Data Error : %@", error);
    }
}
- (IBAction)SchoolDelete:(UIButton *)sender {
    // 创建获取数据的请求对象,指明对Student实体进行删除操做
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 建立谓词对象,过滤出符合要求的对象,也就是要删除的对象
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
    // 执行获取操做,找到要删除的对象
    NSError * error = nil;
    NSArray<Student *> * students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    // 遍历符合删除要求的对象数组,执行删除操做
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext deleteObject:obj];
    }];
    // 保存上下文,并判断当前上下文是否有改动
    if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) {
        BOOL isDeleteSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:nil];
        if (isDeleteSuccess) {
            NSLog(@"SchoolisDeleteSuccess");
        }
    }
    // 错误处理
    if (error) {
        NSLog(@"CoreData Delete Data Error : %@", error);
    }
}
- (IBAction)SchoolUpdate:(UIButton *)sender {
    // 创建获取数据的请求对象,并指明操做的实体为Student
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 建立谓词对象,设置过滤条件
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
    request.predicate = predicate;
    
    // 执行获取请求,获取到符合要求的托管对象
    NSError * error = nil;
    NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    
    // 遍历获取到的数组,并执行修改操做
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        obj.age = @(24);
    }];
    
    // 将上面的修改进行存储
    if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) {
        BOOL isUpdateSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:nil];
        if (isUpdateSuccess) {
            NSLog(@"SchoolIsUpdateSuccess");
        }
    }
    
    // 错误处理
    if (error) {
        NSLog(@"CoreData Update Data Error : %@", error);
    }
    
    /**
     在上面简单的设置了NSPredicate的过滤条件,对于比较复杂的业务需求,还能够设置复合过滤条件,例以下面的例子
     [NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]
     
     也能够经过NSCompoundPredicate对象来设置复合过滤条件
     [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]
     */
}
- (IBAction)SchoolSearch:(UIButton *)sender {
    // 创建获取数据的请求对象,指明操做的实体为Student
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    // 执行获取操做,获取全部Student托管对象
    NSError * error = nil;
    NSArray<Student *> * students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    
    // 遍历输出查询结果
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Student Name : %@, Age : %d", obj.name, obj.age);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"CoreData Ergodic Data Error : %@", error);
    }
}

 

三、CoreData-使用进阶

  CoreData中能够经过设置NSFetchRequest类的predicate属性,来设置一个NSPredicate类型的谓词对象当作过滤条件。经过设置这个过滤条件,能够只获取符合过滤条件的托管对象,不会将全部托管对象都加载到内存中。这样是很是节省内存和加快查找速度的,设计一个好的NSPredicate能够优化CoreData搜索性能。

[NSPredicate predicateWithFormat:@"age >= 30"]

  能够经过NSPredicateiOS中的集合对象执行过滤操做,能够是NSArrayNSSet及其子类。对不可变数组NSArray执行的过滤,过滤后会返回一个NSArray类型的结果数组,其中存储着符合过滤条件的对象。

NSArray *results = [array filteredArrayUsingPredicate:predicate]

谓词不仅能够过滤简单条件,还能够过滤复杂条件,设置复合过滤条件。

[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]

固然也能够经过NSCompoundPredicate对象来设置复合过滤条件,返回结果是一个NSPredicate的子类NSCompoundPredicate对象。

[[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]

NSPredicate中还可使用正则表达式,能够经过正则表达式完成一些复杂需求,这使得谓词的功能更增强大,例以下面是一个手机号验证的正则表达式

NSString *mobile = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
NSPredicate *regexmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", mobile];

NSPredicate支持对数据的模糊查询,例以下面使用通配符来匹配包含lxz的结果,具体CoreData中的使用在下面会讲到。

[NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]

NSPredicate在建立查询条件时,还支持设置被匹配目标的keyPath,也就是设置更深层被匹配的目标。例以下面设置employeename属性为查找条件,就是用点语法设置的keyPath

[NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]

在执行fetch操做前,能够给NSFetchRequest设置一些参数,这些参数包括谓词、排序等条件,下面是一些基础的设置。

  • 设置查找哪一个实体,从数据库的角度来看就是查找哪张表,经过fetchRequestWithEntityName:或初始化方法来指定表名。
  • 经过NSPredicate类型的属性,能够设置查找条件,这个属性在开发中用得最多。NSPredicate能够包括固定格式的条件以及正则表达式
  • 经过sortDescriptors属性,能够设置获取结果数组的排序方式,这个属性是一个数组类型,也就是能够设置多种排序条件。(可是注意条件不要冲突)
  • 经过fetchOffset属性设置从查询结果的第几个开始获取,经过fetchLimit属性设置每次获取多少个。主要用于分页查询,后面会讲。

  MOC执行fetch操做后,获取的结果是以数组的形式存储的,数组中存储的就是托管对象。NSFetchRequest提供了参数resultType,参数类型是一个枚举类型。经过这个参数,能够设置执行fetch操做后返回的数据类型。

设置获取条件

// 创建获取数据的请求对象,并指明操做Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 设置请求条件,经过设置的条件,来过滤出须要的数据
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 设置请求结果排序方式,能够设置一个或一组排序方式,最后将全部的排序方式添加到排序数组中
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
// NSSortDescriptor的操做都是在SQLite层级完成的,不会将对象加载到内存中,因此对内存的消耗是很是小的
request.sortDescriptors = @[sort];

// 执行获取请求操做,获取的托管对象将会被存储在一个数组中并返回
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Employee Name : %@, Height : %@, Brithday : %@", obj.name, obj.height, obj.brithday);
}];

// 错误处理
if (error) {
    NSLog(@"CoreData Fetch Data Error : %@", error);
}

这里设置NSFetchRequest对象的一些请求条件,设置查找Employee表中namelxz的数据,而且将全部符合的数据用height升序的方式排列。

查询操做

// 建立获取数据的请求对象,并指明操做Department表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Department"];

// 设置请求条件,设置employee的name为请求条件。NSPredicate的好处在于,能够设置keyPath条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"];
request.predicate = predicate;

// 执行查找操做
NSError *error = nil;
NSArray<Department *> *departments = [context executeFetchRequest:request error:&error];
[departments enumerateObjectsUsingBlock:^(Department * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Department Search Result DepartName : %@, employee name : %@", obj.departName, obj.employee.name);
}];

// 错误处理
if (error) {
    NSLog(@"Department Search Error : %@", error);
}

查找Department实体,并打印实体内容。就像上面讲的双向关系同样,有关联关系的实体,本身被查找出来后,也会将与之关联的其余实体也查找出来,而且查找出来的实体都是关联着MOC的。

分页查询

在从本地存储区获取数据时,能够指定从第几个获取,以及本次查询获取多少个数据,联合起来使用就是分页查询。固然也能够根据需求,单独使用这两个API

这种需求在实际开发中很是常见,例如TableView中,上拉加载数据,每次加载20条数据,就能够利用分页查询轻松实现。

#pragma mark - ----- Page && Fuzzy ------
//分页查询
- (IBAction)pageSearch:(UIButton *)sender {
    // 建立获取数据的请求对象,并指明操做Student表
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    // 设置查找起始点,这里是从搜索结果的第六个开始获取
    request.fetchOffset = 6;
    
    // 设置分页,每次请求获取六个托管对象
    request.fetchLimit = 6;
    
    // 设置排序规则,这里设置年龄升序排序
    NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
    request.sortDescriptors = @[descriptor];
    
    // 执行查询操做
    NSError * error = nil;
    NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    
    // 遍历输出查询结果
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Page Search Result Name : %@, Age : %d", obj.name, obj.age);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Page Search Data Error : %@", error);
    }
}

上面是一个按照身高升序排序,分页获取搜索结果的例子。查找Employee表中的实体,将结果按照height字段升序排序,并从结果的第六个开始查找,而且设置获取的数量也是六个。

模糊查询

有时须要获取具备某些相同特征的数据,这样就须要对查询的结果作模糊匹配。在CoreData执行模糊匹配时,能够经过NSPredicate执行这个操做。

//模糊查询
- (IBAction)fuzzySearch:(UIButton *)sender {
    // 建立获取数据的请求对象,设置对Student表进行操做
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    // 建立模糊查询条件。这里设置的带通配符的查询,查询条件是结果包含lxz
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
    request.predicate      = predicate;
    
    // 执行查询操做
    NSError * error = nil;
    NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    
    // 遍历输出查询结果
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Fuzzy Search Result Name : %@, Age : %d", obj.name, obj.age);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Fuzzy Search Data Error : %@", error);
    }
    
    /**
     模糊查询的关键在于设置模糊查询条件,除了上面的模糊查询条件,还能够设置下面三种条件
     */
    // 以lxz开头
    // NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"lxz"];
    // 以lxz结尾
    // NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"name ENDSWITH %@"  , @"lxz"];
    // 其中包含lxz
    // NSPredicate *predicate3 = [NSPredicate predicateWithFormat:@"name contains %@"  , @"lxz"];
    // 还能够设置正则表达式做为查找条件,这样使查询条件更增强大,下面只是给了个例子
    // NSString *mobile = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
    // NSPredicate *predicate4 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", mobile];
}

上面是使用通配符的方式进行模糊查询NSPredicate支持多种形式的模糊查询,下面列举一些简单的匹配方式。模糊查询条件对大小写不敏感,因此查询条件大小写都可。

加载请求模板

在以前的文章中谈到在模型文件中设置请求模板,也就是在.xcdatamodeld文件中,设置Fetch Requests,使用时能够经过对应的NSManagedObjectModel获取设置好的模板。

#pragma mark - ----- Fetch Request ------
/**
 加载模型文件中设置的FetchRequest请求模板,模板名为StudentAge,在School.xcdatamodeld中设置
 */
- (IBAction)fetchRequest:(UIButton *)sender {
    // 经过MOC获取托管对象模型,托管对象模型至关于.xcdatamodeld文件,存储着.xcdatamodeld文件的结构
    NSManagedObjectModel * model = [CoreDataManager sharedCoreDataManager].persistentContainer.managedObjectModel;
    
    // 经过.xcdatamodeld文件中设置的模板名,获取请求对象
    NSFetchRequest * fetchRequest = [model fetchRequestTemplateForName:@"StudentAge"];
    
    // 请求数据,下面的操做和普通请求同样
    NSError *error = nil;
    NSArray<Student *> *dataList = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error];
    
    // 遍历获取结果,并打印结果
    [dataList enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Student.count = %ld, Student.age = %d", dataList.count, obj.age);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Execute Fetch Request Error : %@", error);
    }
}

请求结果排序

/**
 对请求结果进行排序
 这个排序是发生在数据库一层的,并非将结果取出后排序,因此效率比较高
 */
- (IBAction)resultSort:(UIButton *)sender {
    // 创建获取数据的请求对象,并指明操做Student表
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 设置请求条件,经过设置的条件,来过滤出须要的数据
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
    request.predicate = predicate;
    // 设置请求结果排序方式,能够设置一个或一组排序方式,最后将全部的排序方式添加到排序数组中
    NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
    // NSSortDescriptor的操做都是在SQLite层级完成的,不会将对象加载到内存中,因此对内存的消耗是很是小的
    // 下面request的sort对象是一个数组,也就是能够设置多种排序条件,但注意条件不要冲突
    request.sortDescriptors = @[sort];
    // 执行获取请求操做,获取的托管对象将会被存储在一个数组中并返回
    NSError * error = nil;
    NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
    // 遍历返回结果
    [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Employee Name : %@, Age : %d", obj.name, obj.age);
    }];
    // 错误处理
    if (error) {
        NSLog(@"CoreData Fetch Data Error : %@", error);
    }
}

获取结果Count值

开发过程当中有时须要只获取所需数据的Count值,也就是执行获取操做后数组中所存储的对象数量。遇到这个需求,若是像以前同样MOC执行获取操做,获取到数组而后取Count,这样对内存消耗是很大的

对于这个需求,苹果提供了两种经常使用的方式获取这个Count值。这两种获取操做,都是在数据库中完成的,并不须要将托管对象加载到内存中,对内存的开销也是很小的。

方法1,设置resultType

/**
 获取返回结果的Count值,经过设置NSFetchRequest的resultType属性
 */
- (IBAction)getResultCount1:(UIButton *)sender {
    // 设置过滤条件,能够根据需求设置本身的过滤条件
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < 24"];
    // 建立请求对象,并指明操做Student表
    NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    fetchRequest.predicate = predicate;
    // 这一步是关键。设置返回结果类型为Count,返回结果为NSNumber类型
    fetchRequest.resultType = NSCountResultType;
    // 执行查询操做,返回的结果仍是数组,数组中只存在一个对象,就是计算出的Count值
    NSError * error = nil;
    NSArray * dataList = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error];
    // 返回结果存在数组的第一个元素中,是一个NSNumber的对象,经过这个对象便可得到Count值
    NSInteger count = [dataList.firstObject integerValue];
    NSLog(@"fetch request result Employee.count = %ld", count);
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
}

方法1中设置NSFetchRequest对象的resultTypeNSCountResultType,获取到结果的Count值。这个枚举值在以前的文章中提到过,除了Count参数,还能够设置其余三种参数。

方法2,使用MOC提供的方法

- (IBAction)getResultCount2:(UIButton *)sender {
    // 设置过滤条件
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < 24"];
    // 建立请求对象,指明操做Student表
    NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    fetchRequest.predicate = predicate;
    // 经过调用MOC的countForFetchRequest:error:方法,获取请求结果count值,返回结果直接是NSUInteger类型变量
    NSError * error = nil;
    NSUInteger count = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext countForFetchRequest:fetchRequest error:&error];
    NSLog(@"fetch request result count is : %ld", count);
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
}

MOC提供了专门获取请求结果Count值的方法,经过这个方法能够直接返回一个NSUInteger类型的Count值,使用起来比上面的方法更方便点,其余都是同样的。

位运算

假设有需求是对Employee表中,全部托管对象的height属性计算总和。这个需求在数据量比较大的状况下,将全部托管对象加载到内存中是很是消耗内存的,就算批量加载也比较耗时耗内存。

CoreData对于这样的需求,提供了位运算的功能。MOC在执行请求时,是支持对数据进行位运算的。这个操做依然是在数据库层完成的,对内存的占用很是小。

/**
 对返回的结果进行按位运算,这个运算是发生在SQLite数据库层的,因此执行效率很快,对内存的消耗也很小
 若是须要对托管对象的某个属性进行运算,比较推荐这种效率高的方法.
 */
- (IBAction)bitwiseArithmetic:(UIButton *)sender {
    // 建立请求对象,指明操做Student表
    NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 设置返回值为字典类型,这是为告终果能够经过设置的name名取出,这一步是必须的
    fetchRequest.resultType = NSDictionaryResultType;
    // 建立描述对象的name字符串
    NSString * descriptionName = @"sumOperatin";
    // 建立描述对象
    NSExpressionDescription * expressionDes = [[NSExpressionDescription alloc] init];
    // 设置描述对象的name,最后结果须要用这个name当作key来取出结果
    expressionDes.name = descriptionName;
    // 设置返回值类型,根据运算结果设置类型
    expressionDes.expressionResultType = NSInteger16AttributeType;
    // 建立具体描述对象,用来描述对哪一个属性进行什么运算(可执行的运算类型不少,这里描述的是对age属性,作sum运算)
    NSExpression * expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
    // 只能对应一个具体描述对象
    expressionDes.expression = expression;
    // 给请求对象设置描述对象,这里是一个数组类型,也就是能够设置多个描述对象
    fetchRequest.propertiesToFetch = @[expressionDes];
    // 执行请求,返回值仍是一个数组,数组中只有一个元素,就是存储计算结果的字典
    NSError * error = nil;
    NSArray * resultArr = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error];
    // 经过上面设置的name值,当作请求结果的key取出计算结果
    NSNumber * number = resultArr.firstObject[descriptionName];
    NSLog(@"fetch request result is %ld", [number integerValue]);
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
    
    /**
     位运算支持的算法种类不少,具体能够在NSExpression.h文件中查看
     */
}

从执行结果能够看到,MOC对全部查找到的托管对象height属性执行了求和操做,并将结果放在字典中返回。位运算主要是经过NSFetchRequest对象的propertiesToFetch属性设置,这个属性能够设置多个描述对象,最后经过不一样的name当作key来取出结果便可。

NSExpression类能够描述多种运算,能够在NSExpression.h文件中的注释部分,看到全部支持的运算类型,大概看了一下有二十多种运算。并且除了上面NSExpression调用的方法,此类还支持点语法的位运算,例以下面的例子。

[NSExpression expressionWithFormat:@"@sum.height"];

批处理

  在使用CoreData以前,我和公司同事也讨论过,假设遇到须要大量数据处理的时候怎么办。CoreData对于大量数据处理的灵活性确定不如SQLite,这时候还须要本身使用其余方式优化数据处理。虽然在移动端这种状况不多出现,可是在持久层设计时仍是要考虑这方面。

  当须要进行数据的处理时,CoreData须要先将数据加载到内存中,而后才能对数据进行处理。这样对于大量数据来讲,都加载到内存中是很是消耗内存的,并且容易致使崩溃的发生。若是遇到更改全部数据的某个字段这样的简单需求,须要将相关的托管对象都加载到内存中,而后进行更改、保存。

  对于上面这样的问题,CoreDataiOS8推出了批量更新API,经过这个API能够直接在数据库一层就完成更新操做,而不须要将数据加载到内存。除了批量更新操做,在iOS9中还推出了批量删除API,也是在数据库一层完成的操做。关于批处理的API不少都是iOS8iOS9出来的,使用时须要注意版本兼容

  可是有个问题,批量更新和批量删除的两个API,都是直接对数据库进行操做,更新完以后会致使MOC缓存和本地持久化数据不一样步的问题。因此须要手动刷新受影响的MOC中存储的托管对象,使MOC和本地统一。假设你使用了NSFetchedResultsController,为了保证界面和数据的统一,这一步更新操做更须要作。

批量更新

#pragma mark - ----- Batch Operation ------
/**
 注意:不管是批量更新仍是批量删除,这个批量操做都是发生在SQLite层的。然而在SQLite发生了批量操做后,并不会主动更新上层MOC中缓存的托管对象,因此在进行批量操做后,须要对相关的MOC进行更新操做。
 虽然在客户端不多遇到大量数据处理的状况,可是若是遇到这样的需求,推荐使用批量处理API。
 */

/**
 批量更新
 */
- (IBAction)batchUpdate:(UIButton *)sender {
    // 建立批量更新对象,并指明操做Student表
    NSBatchUpdateRequest * updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Student"];
    // 设置返回值类型,默认是什么都不返回(NSStatusOnlyResultType),这里设置返回发生改变的对象Count值
    updateRequest.resultType = NSUpdatedObjectsCountResultType;
    // 设置发生改变字段的字典
    updateRequest.propertiesToUpdate = @{@"name" : @"lxz"};
    // 执行请求后,返回值是一个特定的result对象,经过result的属性获取返回的结果。
    // MOC的这个API是从iOS8出来的,因此须要注意版本兼容。
    NSError * error = nil;
    NSBatchUpdateResult * result = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeRequest:updateRequest error:&error];
    NSLog(@"batch update count is %ld", [result.result integerValue]);
    // 错误处理
    if (error) {
        NSLog(@"batch update request result error : %@", error);
    }
    // 更新MOC中的托管对象,使MOC和本地持久化区数据同步
    [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext refreshAllObjects];
}

上面对Employee表中全部的托管对象height值作了批量更新,在更新时经过设置propertiesToUpdate字典来控制更新字段和更新的值,设置格式是字段名 : 新值。经过设置批处理对象的predicate属性,设置一个谓词对象来控制受影响的对象

还能够对多个存储区(数据库)作一样批处理操做,经过设置其父类affectedStores属性,类型是一个数组,能够包含受影响的存储区,多个存储区的操做对批量删除一样适用

MOC在执行请求方法时,发现方法名也不同了,执行的是executeRequest: error:方法,这个方法是从iOS8以后出来的。方法传入的参数是NSBatchUpdateRequest类,此类并非继承自NSFetchRequest类,而是直接继承自NSPersistentStoreRequest,和NSFetchRequest是平级关系。

批量删除

/**
 批量删除
 */
- (IBAction)batchDelete:(UIButton *)sender {
    // 建立请求对象,并指明对Student表作操做
    NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 经过谓词设置过滤条件,设置条件为age小于20
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < %ld", 20];
    fetchRequest.predicate = predicate;
    // 建立批量删除请求,并使用上面建立的请求对象当作参数进行初始化
    NSBatchDeleteRequest * deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
    // 设置请求结果类型,设置为受影响对象的Count
    deleteRequest.resultType = NSBatchDeleteResultTypeCount;
    // 使用NSBatchDeleteResult对象来接受返回结果,经过id类型的属性result获取结果
    NSError * error = nil;
    NSBatchDeleteResult * result = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeRequest:deleteRequest error:&error];
    NSLog(@"batch delete request result count is %ld", [result.result integerValue]);
    // 错误处理
    if (error) {
        NSLog(@"batch delete request error : %@", error);
    }
    // 更新MOC中的托管对象,使MOC和本地持久化区数据同步
    [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext refreshAllObjects];
}

大多数状况下,涉及到托管对象的操做,都须要将其加载到内存中完成。因此使用CoreData时,须要注意内存的使用,不要在内存中存在过多的托管对象。在已经作系统兼容的状况下,进行大量数据的操做时,应该尽可能使用批处理来完成操做。

须要注意的是,refreshAllObjects是从iOS9出来的,在iOS9以前由于要作版本兼容,因此须要使用refreshObject: mergeChanges:方法更新托管对象。

异步请求

#pragma mark - ----- Asynchronous Request ------
/**
 异步处理
 */
- (IBAction)asyncRequest:(UIButton *)sender {
    // 建立请求对象,并指明操做Student表
    NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    // 建立异步请求对象,并经过一个block进行回调,返回结果是一个NSAsynchronousFetchResult类型参数
    NSAsynchronousFetchRequest * asycFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult * _Nonnull result) {
        // 经过返回结果的finalResult属性,获取结果数组
        [result.finalResult enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"fetch request result Student.count = %ld, Student.name = %@", result.finalResult.count, obj.name);
        }];
    }];
    
    // 执行异步请求,和批量处理执行同一个请求方法
    NSError * error = nil;
    [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeRequest:asycFetchRequest error:&error];
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
}

上面经过NSAsynchronousFetchRequest对象建立了一个异步请求,并经过block进行回调。若是有多个请求同时发起不须要担忧线程安全的问题,系统会将全部的异步请求添加到一个操做队列中,在前一个任务访问数据库时,CoreData会将数据库加锁,等前面的执行完成才会继续执行后面的操做。

NSAsynchronousFetchRequest提供了cancel方法,也就是能够在请求过程当中,将这个请求取消。还能够经过一个NSProgress类型的属性,获取请求完成进度。NSAsynchronousFetchRequest类从iOS8开始可使用,因此低版本须要作版本兼容。

须要注意的是,执行请求时MOC并发类型不能是NSConfinementConcurrencyType,这个并发类型已经被抛弃,会致使崩溃。

 

四、CoreData-高级用法

NSFetchedResultsController

  在开发过程当中会常常用到UITableView这样的视图类,这些视图类须要本身管理其数据源,包括网络获取、本地存储都须要写代码进行管理。

  而在CoreData中提供了NSFetchedResultsController类(fetched results controller,也叫FRC),FRC能够管理UITableViewUICollectionView的数据源。这个数据源主要指本地持久化的数据,也能够用这个数据源配合着网络请求数据一块儿使用,主要看业务需求了。

  本篇文章会使用UITableView做为视图类,配合NSFetchedResultsController进行后面的演示,UICollectionView配合NSFetchedResultsController的使用也是相似,这里就不都讲了。

简单介绍

  就像上面说到的,NSFetchedResultsController就像是上面两种视图的数据管理者同样。FRC能够监听一个MOC的改变,若是MOC执行了托管对象的增删改操做,就会对本地持久化数据发生改变,FRC就会回调对应的代理方法,回调方法的参数会包括执行操做的类型、操做的值、indexPath等参数。

  实际使用时,经过FRC“绑定”一个MOC,将UITableView嵌入在FRC的执行流程中。在任何地方对这个“绑定”MOC存储区作修改,都会触发FRC的回调方法,在FRC的回调方法中嵌入UITableView代码并作对应修改便可。

  由此能够看出FRC最大优点就是,始终和本地持久化的数据保持统一。只要本地持久化的数据发生改变,就会触发FRC的回调方法,从而在回调方法中更新上层数据源和UI。这种方式讲的简单一点,就能够叫作数据带动UI



可是须要注意一点,在FRC的初始化中传入了一个MOC参数,FRC只能监测传入的MOC发生的改变。假设其余MOC对同一个存储区发生了改变,FRC则不能监测到这个变化,不会作出任何反应。

因此使用FRC时,须要注意FRC只能对一个MOC的变化作出反应,因此在CoreData持久化层设计时,尽可能一个存储区只对应一个MOC,或设置一个负责UIMOC,这在后面多线程部分会详细讲解。

修改模型文件结构

在写代码以前,先对以前的模型文件结构作一些修改。



FRC的时候,只须要用到Employee这一张表,其余表和设置直接忽略。须要在Employee原有字段的基础上,增长一个String类型的sectionName字段,这个字段就是用来存储section title的,在下面的文章中将会详细讲到。

初始化FRC

下面例子是比较经常使用的FRC初始化方式,初始化时指定的MOC,还用以前讲过的MOC初始化代码,UITableView初始化代码这里也省略了,主要突出FRC的初始化。

#import "ChatViewController.h"
#import "CoreDataManager.h"
#import "User+CoreDataProperties.h"

@interface ChatViewController ()<UITableViewDataSource, UITableViewDelegate,NSFetchedResultsControllerDelegate>
@property (nonatomic, strong) CoreDataManager * manager;
@property (nonatomic, strong) User * user;
@property (nonatomic, strong) UITableView * tableView;
@property (strong, nonatomic) NSFetchedResultsController * fetchedResultController;
@end

@implementation ChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _manager = [CoreDataManager sharedCoreDataManager];
    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, self.view.bounds.size.width, self.view.bounds.size.height-100) style:UITableViewStylePlain];
    [self.view addSubview:_tableView];
    _tableView.delegate = self;
    _tableView.dataSource = self;
}

#pragma mark - ----- UITableView Delegate ------
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.fetchedResultController.sections.count;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    _user = [self.fetchedResultController objectAtIndexPath:indexPath];
    
    UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"identifier"];
    }
    cell.textLabel.text = _user.username;
    cell.detailTextLabel.text = _user.age;
    return cell;
}

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

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

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

#pragma mark - ----- NSFetchedResultsController ------
#pragma mark - ----- 生成测试数据 ------
//插入数据
- (IBAction)CreateTestData:(UIButton *)sender {
    for (int i = 0; i < 3; i++) {
        _user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext];
        _user.username = [NSString stringWithFormat:@"username:%d", i];
        _user.age = [NSString stringWithFormat:@"age:%d", i ];
        _user.sectionName = [NSString stringWithFormat:@"sectionName:%d", i];
    }
    
    NSError * error = nil;
    if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) {
        [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
    }
}

- (IBAction)RefreshTestData:(UIButton *)sender {
    // 建立请求对象,并指明操做User表
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"User"];
    // 设置排序规则,指明根据age字段升序排序
    NSSortDescriptor * ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
    request.sortDescriptors = @[ageSort];
    // 建立NSFetchedResultsController控制器实例,并绑定MOC
    NSError * error = nil;
    self.fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext sectionNameKeyPath:@"sectionName" cacheName:nil];
    // 设置代理,并遵照协议
    self.fetchedResultController.delegate = self;
    // 执行获取请求,执行后FRC会从持久化存储区加载数据,其余地方能够经过FRC获取数据
    [self.fetchedResultController performFetch:&error];
    // 错误处理
    if (error) {
        NSLog(@"NSFetchedResultsController init error : %@", error);
    }
    
    // 刷新UI
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"identifier"];
    [self.tableView reloadData];
}

#pragma mark - ----- NSFetchedResultsControllerDelegate ------

// Cell数据源发生改变会回调此方法,例如添加新的托管对象等
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeMove:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeUpdate: {
            User * user = [self.fetchedResultController objectAtIndexPath:indexPath];
            
            UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.textLabel.text = user.username;
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
            break;
    }
}

// Section数据源发生改变回调此方法,例如修改section title等
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        default:
            break;
    }
}

// 本地数据源发生改变,将要开始回调FRC代理方法。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}

// 本地数据源发生改变,FRC代理方法回调完成。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
}

- (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
    return [NSString stringWithFormat:@"sectionName %@", sectionName];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

就像cellForRowAtIndexPath:方法中使用的同样,FRC提供了两个方法轻松转换indexPathNSManagedObject的对象,在实际开发中这两个方法很是实用,这也是FRCUITableViewUICollectionView深度融合的表现。

- (id)objectAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)indexPathForObject:(id)object;
Fetched Results Controller Delegate
// 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 <NSFetchedResultsSectionInfo>)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];
}

上面就是当本地持久化数据发生改变后,被回调的FRC代理方法的实现,能够在对应的实现中完成本身的代码逻辑。

在上面的章节中讲到删除cell后,本地持久化数据同步的问题。在删除cell后在tableView代理方法的回调中,调用了MOC的删除方法,使本地持久化存储和UI保持同步,并回调到下面的FRC代理方法中,在代理方法中对UI作删除操做,这样一套由UI的改变引起的删除流程就完成了。

目前为止已经实现了数据和UI双向同步,即UI发生改变后本地存储发生改变,本地存储发生改变后UI也随之改变。能够经过下面添加数据的代码来测试一下,NSFetchedResultsController就讲到这里了。

- (void)addMoreData {
    Employee *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
    employee.name = [NSString stringWithFormat:@"lxz 15"];
    employee.height = @(15);
    employee.brithday = [NSDate date];
    employee.sectionName = [NSString stringWithFormat:@"3"];

    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"MOC save error : %@", error);
    }
}

版本迁移

CoreData版本迁移的方式有不少,通常都是先在Xcode中,原有模型文件的基础上,建立一个新版本的模型文件,而后在此基础上作不一样方式的版本迁移。

本章节将会讲三种不一样的版本迁移方案,但都不会讲太深,都是从使用的角度讲起,能够知足大多数版本迁移的需求。

为何要版本迁移?

  在已经运行程序并经过模型文件生成数据库后,再对模型文件进行的修改,若是只是修改已有实体属性的默认值、最大最小值、Fetch Request等属性自身包含的参数时,并不会发生错误。若是修改模型文件的结构,或修改属性名、实体名等,形成模型文件的结构发生改变,这样再次运行程序就会致使崩溃

  在开发测试过程当中,能够直接将原有程序卸载就能够解决这个问题,可是本地以前存储的数据也会消失。若是是线上程序,就涉及到版本迁移的问题,不然会致使崩溃,并提示以下错误:

 

CoreData: error: Illegal attempt to save to a file that was never opened. "This NSPersistentStoreCoordinator has no persistent stores (unknown).  It cannot perform a save operation.". No last error recorded.

 

然而在需求不断变化的过程当中,后续版本确定会对原有的模型文件进行修改,这时就须要用到版本迁移的技术,下面开始讲版本迁移的方案。

建立新版本模型文件

本文中讲的几种版本迁移方案,在迁移以前都须要对原有的模型文件建立新版本。

选中须要作迁移的模型文件 -> 点击菜单栏Editor -> Add Model Version -> 选择基于哪一个版本的模型文件(通常都是选择目前最新的版本),新建模型文件完成。

对于新版本模型文件的命名,我在建立新版本模型文件时,通常会拿当前工程版本号当作后缀,这样在模型文件版本比较多的时候,就能够很容易将模型文件版本和工程版本对应起来

 

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

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

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

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

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

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

 

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

轻量级版本迁移

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

字典中设置的key
  • NSMigratePersistentStoresAutomaticallyOption设置为YESCoreData会试着把低版本的持久化存储区迁移到最新版本的模型文件。
  • NSInferMappingModelAutomaticallyOption设置为YESCoreData会试着以最为合理地方式自动推断出源模型文件的实体中,某个属性到底对应于目标模型文件实体中的哪个属性。

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

// 设置版本迁移方案
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                                NSInferMappingModelAutomaticallyOption : @YES};

// 建立持久化存储协调器,并将迁移方案的字典当作参数传入
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:options error:nil];
修改实体名

假设须要对已存在实体进行更名操做,须要将重命名后的实体Renaming ID,设置为以前的实体名。下面是Employee实体进行操做。

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

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

Mapping Model 迁移方案

轻量级迁移方案只是针对增长和改变实体、属性这样的一些简单操做,假设有更复杂的迁移需求,就应该使用Xcode提供的迁移模板(Mapping Model)。经过Xcode建立一个后缀为.xcmappingmodel的文件,这个文件是专门用来进行数据迁移用的,一些变化关系也会体如今模板中,看起来很是直观

这里还以上面更改实体名,并迁移实体数据为例子,将Employee实体迁移到Employee2中。首先将Employee实体更名为Employee2,而后建立Mapping Model文件。

Command + N 新建文件 -> 选择 Mapping Model -> 选择源文件 Source Model -> 选择目标文件 Target Model -> 命名 Mapping Model 文件名 -> Create 建立完成。

  如今就建立好一个Mapping Model文件,文件中显示了实体、属性、Relationships,源文件和目标文件之间的关系。实体命名是EntityToEntity的方式命名的,实体包含的属性和关联关系,都会被添加到迁移方案中(Entity MappingAttribute MappingRelationship Mapping)。

在迁移文件的下方是源文件和目标文件的关系。



  在上面图中更名后的Employee2实体并无迁移关系,因为是更名后的实体,系统还不知道实体应该怎样作迁移。因此选中Mapping Model文件的Employee2 Mappings,能够看到右侧边栏的Sourceinvalid value。由于要从Employee实体迁移数据过来,因此将其选择为Employee,迁移关系就设置完成了。

  设置完成后,还应该将以前EmployeeToEmployeeMappings删除,由于这个实体已经被Employee2替代,它的Mappings也被Employee2 Mappings所替代,不然会报错。



  在实体的迁移过程当中,还能够经过设置Predicate的方式,来简单的控制迁移过程。例如只须要迁移一部分指定的数据,就能够经过Predicate来指定。能够直接在右侧Filter Predicate的位置设置过滤条件,格式是$source.height < 100$source表明数据源的实体。

更复杂的迁移需求

  若是还存在更复杂的迁移需求,并且上面的迁移方式不能知足,能够考虑更复杂的迁移方式。假设要在迁移过程当中,对迁移的数据进行更改,这时候上面的迁移方案就不能知足需求了。

  对于上面提到的问题,在Mapping Model文件中选中实体,能够看到Custom Policy这个选项,选项对应的是NSEntityMigrationPolicy的子类,能够建立并设置一个子类,并重写这个类的方法来控制迁移过程。

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error;

版本迁移总结

版本迁移在需求的变动中确定是要发生的,可是咱们应该尽可能避免这样的状况发生。在最开始设计模型文件数据结构的时候,就应该设计一个比较完善而且容易应对变化的结构,这样后面就算发生变化也不会对结构主体形成大的改动。



 

五、CoreData-多线程

CoreData的多线程,其中会包括并发队列类型、线程安全等技术点。

MOC并发队列类型

  在CoreDataMOC是支持多线程的,能够在建立MOC对象时,指定其并发队列的类型。当指定队列类型后,系统会将操做都放在指定的队列中执行,若是指定的是私有队列,系统会建立一个新的队列。但这都是系统内部的行为,咱们并不能获取这个队列,队列由系统所拥有,并由系统将任务派发到这个队列中执行的

NSManagedObjectContext并发队列类型:
  • NSConfinementConcurrencyType : 若是使用init方法初始化上下文,默认就是这个并发类型。这个枚举值是不支持多线程的,从名字上也体现出来了。
  • NSPrivateQueueConcurrencyType : 私有并发队列类型,操做都是在子线程中完成的。
  • NSMainQueueConcurrencyType : 主并发队列类型,若是涉及到UI相关的操做,应该考虑使用这个枚举值初始化上下文。

其中NSConfinementConcurrencyType类型在iOS9以后已经被苹果废弃,不建议使用这个API。使用此类型建立的MOC,调用某些比较新的CoreDataAPI可能会致使崩溃。

MOC多线程调用方式

  在CoreDataMOC不是线程安全的,在多线程状况下使用MOC时,不能简单的将MOC从一个线程中传递到另外一个线程中使用,这并非CoreData的多线程,并且会出问题。对于MOC多线程的使用,苹果给出了本身的解决方案。

  在建立的MOC中使用多线程,不管是私有队列仍是主队列,都应该采用下面两种多线程的使用方式,而不是本身手动建立线程。调用下面方法后,系统内部会将任务派发到不一样的队列中执行。能够在不一样的线程中调用MOC的这两个方法,这个是容许的。

- (void)performBlock:(void (^)())block            异步执行的block,调用以后会马上返回。
- (void)performBlockAndWait:(void (^)())block     同步执行的block,调用以后会等待这个任务完成,才会继续向下执行。

下面是多线程调用的示例代码,在多线程的环境下执行MOCsave方法,就是将save方法放在MOCblock体中异步执行,其余方法的调用也是同样的。

[context performBlock:^{
    [context save:nil];
}];

可是须要注意的是,这两个block方法不能在NSConfinementConcurrencyType类型的MOC下调用,这个类型的MOC是不支持多线程的,只支持其余两种并发方式的MOC

多线程的使用

在业务比较复杂的状况下,须要进行大量数据处理,而且还须要涉及到UI的操做。对于这种复杂需求,若是都放在主队列中,对性能和界面流畅度都会有很大的影响,致使用户体验很是差,下降屏幕FPS。对于这种状况,能够采起多个MOC配合的方式。

CoreData多线程的发展中,在iOS5经历了一次比较大的变化,以后能够更方便的使用多线程。从iOS5开始,支持设置MOCparentContext属性,经过这个属性能够设置MOC父MOC。下面会针对iOS5以前和以后,分别讲解CoreData的多线程使用。

尽管如今的开发中早就不兼容iOS5以前的系统了,可是做为了解这里仍是要讲一下,并且这种同步方式在iOS5以后也是能够正常使用的,也有不少人还在使用这种同步方式,下面其余章节也是同理。

iOS5以前使用多个MOC

iOS5以前实现MOC的多线程,能够建立多个MOC,多个MOC使用同一个PSC,并让多个MOC实现数据同步。经过这种方式不用担忧PSC在调用过程当中的线程问题,MOC在使用PSC进行save操做时,会对PSC进行加锁,等当前加锁的MOC执行完操做以后,其余MOC才能继续执行操做。

每个PSC都对应着一个持久化存储区,PSC知道存储区中数据存储的数据结构,而MOC须要使用这个PSC进行save操做的实现。



  这样作有一个问题,当一个MOC发生改变并持久化到本地时,系统并不会将其余MOC缓存在内存中的NSManagedObject对象改变。因此这就须要咱们在MOC发生改变时,将其余MOC数据更新。

  根据上面的解释,在下面例子中建立了一个主队列的mainMOC,主要用于UI操做。一个私有队列的backgroundMOC,用于除UI以外的耗时操做,两个MOC使用的同一个PSC

简单状况下的数据同步

简单状况下的数据同步,是针对于只有一个MOC的数据发生改变,并提交存储区后,其余MOC更新时并无对相同的数据作改变,只是单纯的同步数据的状况。

NSManagedObjectContext类中,根据不一样操做定义了一些通知。在一个MOC发生改变时,其余地方能够经过MOC中定义的通知名,来获取MOC发生的改变。在NSManagedObjectContext中定义了下面三个通知:

  • NSManagedObjectContextWillSaveNotification MOC将要向存储区存储数据时,调用这个通知。在这个通知中不能获取发生改变相关的NSManagedObject对象。
  • NSManagedObjectContextDidSaveNotification MOC向存储区存储数据后,调用这个通知。在这个通知中能够获取改变、添加、删除等信息,以及相关联的NSManagedObject对象。
  • NSManagedObjectContextObjectsDidChangeNotificationMOC中任何一个托管对象发生改变时,调用这个通知。例如修改托管对象的属性。

经过监听NSManagedObjectContextDidSaveNotification通知,获取全部MOCsave操做。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(settingsContext:) name:NSManagedObjectContextDidSaveNotification object:nil];

不须要在通知的回调方法中,编写代码对比被修改的托管对象。MOC为咱们提供了下面的方法,只须要将通知对象传入,系统会自动同步数据

- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;

下面是通知中的实现代码,可是须要注意的是,因为通知是同步执行的,在通知对应的回调方法中所处的线程,和发出通知的MOC执行操做时所处的线程是同一个线程,也就是系统performBlock:回调方法分配的线程。

因此其余MOC在通知回调方法中,须要注意使用performBlock:方法,并在block体中执行操做。

- (void)settingsContext:(NSNotification *)noti {
    [context performBlock:^{
        // 调用须要同步的MOC对象的merge方法,直接将通知对象当作参数传进去便可,系统会完成同步操做。
        [context mergeChangesFromContextDidSaveNotification:noti];
    }];
}

复杂状况下的数据同步

在一个MOC对本地存储区的数据发生改变,而其余MOC也对一样的数据作了改变,这样后面执行save操做的MOC就会冲突,并致使后面的save操做失败,这就是复杂状况下的数据合并。

这是由于每次一个MOC执行一次fetch操做后,会保存一个本地持久化存储的状态,当下次执行save操做时会对比这个状态和本地持久化状态是否同样。若是同样,则表明本地没有其余MOC对存储发生过改变;若是不同,则表明本地持久化存储被其余MOC改变过,这就是形成冲突的根本缘由。

对于这种冲突的状况,能够经过MOC对象指定解决冲突的方案,经过mergePolicy属性来设置方案。mergePolicy属性有下面几种可选的策略,默认是NSErrorMergePolicy方式,这也是惟一一个有NSError返回值的选项。

  • NSErrorMergePolicy : 默认值,当出现合并冲突时,返回一个NSError对象来描述错误,而MOC和持久化存储区不发生改变
  • NSMergeByPropertyStoreTrumpMergePolicy : 以本地存储为准,使用本地存储来覆盖冲突部分。
  • NSMergeByPropertyObjectTrumpMergePolicy : 以MOC的为准,使用MOC来覆盖本地存储的冲突部分。
  • NSOverwriteMergePolicy : 以MOC为准,用MOC的全部NSManagedObject对象覆盖本地存储的对应对象。
  • NSRollbackMergePolicy : 以本地存储为准,MOC全部的NSManagedObject对象被本地存储的对应对象所覆盖。

上面五种策略中,除了第一个NSErrorMergePolicy的策略,其余四种中NSMergeByPropertyStoreTrumpMergePolicyNSRollbackMergePolicy,以及NSMergeByPropertyObjectTrumpMergePolicyNSOverwriteMergePolicy看起来是重复的。

其实它们并非冲突的,这四种策略的不一样体如今,对没有发生冲突的部分应该怎么处理NSMergeByPropertyStoreTrumpMergePolicyNSMergeByPropertyObjectTrumpMergePolicy对没有冲突的部分,未冲突部分数据并不会受到影响。而NSRollbackMergePolicyNSOverwriteMergePolicy则是不管是否冲突,直接所有替换。

题外话:
对于MOC的这种合并策略来看,有木有感受到CoreData解决冲突的方式,和SVN解决冲突的方式特别像。。。

线程安全

不管是MOC仍是托管对象,都不该该在其余MOC的线程中执行操做,这两个API都不是线程安全的。但MOC能够在其余MOC线程中调用performBlock:方法,切换到本身的线程执行操做。

若是其余MOC想要拿到托管对象,并在本身的队列中使用托管对象,这是不容许的,托管对象是不能直接传递到其余MOC的线程的。可是能够经过获取NSManagedObjectNSManagedObjectID对象,在其余MOC中经过NSManagedObjectID对象,从持久化存储区中获取NSManagedObject对象,这样就是容许的。NSManagedObjectID是线程安全,而且能够跨线程使用的。

能够经过MOC获取NSManagedObjectID对应的NSManagedObject对象,例以下面几个MOCAPI

NSManagedObject *object = [context objectRegisteredForID:objectID];
NSManagedObject *object = [context objectWithID:objectID];

经过NSManagedObject对象的objectID属性,获取NSManagedObjectID类型的objectID对象。

NSManagedObjectID *objectID = object.objectID;

CoreData多线程结构设计

上面章节中写的大多都是怎么用CoreData多线程,在掌握多线程的使用后,就能够根据公司业务需求,设计一套CoreData多线程结构了。对于多线程结构的设计,应该本着尽可能减小主线程压力的角度去设计,将全部耗时操做都放在子线程中执行。

对于具体的设计我根据不一样的业务需求,给出两种设计方案的建议。

两层设计方案

在项目中多线程操做比较简单时,能够建立一个主队列mainMOC,和一个或多个私有队列的backgroundMOC。将全部backgroundMOCparentContext设置为mainMOC,采起这样的两层设计通常就可以知足大多数需求了。



将耗时操做都放在backgroundMOC中执行,mainMOC负责全部和UI相关的操做。全部和UI无关的工做都交给backgroundMOC,在backgroundMOC对数据发生改变后,调用save方法会将改变pushmainMOC中,再由mainMOC执行save方法将改变保存到存储区。

代码这里就不写了,和上面例子中设置parentContext代码同样,主要讲一下设计思路。

三层设计方案

可是咱们发现,上面的save操做最后仍是由mainMOC去执行的,backgroundMOC只是负责处理数据。虽然mainMOC只执行save操做并不会很耗时,可是若是save涉及的数据比较多,这样仍是会对性能形成影响的。

虽然客户端不多涉及到大量数据处理的需求,可是假设有这样的需求。能够考虑在两层结构之上,给mainMOC之上再添加一个parentMOC,这个parentMOC也是私有队列的MOC,用于处理save操做。



这样CoreData存储的结构就是三层了,最底层是backgroundMOC负责处理数据,中间层是mainMOC负责UI相关操做,最上层也是一个backgroundMOC负责执行save操做。这样就将影响UI的全部耗时操做全都剥离到私有队列中执行,使性能达到了很好的优化。

须要注意的是,执行MOC相关操做时,不要阻塞当前主线程。全部MOC的操做应该是异步的,不管是子线程仍是主线程,尽可能少的使用同步block方法。

MOC同步时机

设置MOCparentContext属性以后,parent对于child的改变是知道的,可是child对于parent的改变是不知道的。苹果这样设计,应该是为了更好的数据同步。

Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:backgroundMOC];
emp.name = @"lxz";
emp.brithday = [NSDate date];
emp.height = @1.7f;

[backgroundMOC performBlock:^{
    [backgroundMOC save:nil];
    [mainMOC performBlock:^{
        [mainMOC save:nil];
    }];
}];

在上面这段代码中,mainMOCbackgroundMOCparentContext。在backgroundMOC执行save方法前,backgroundMOCmainMOC都不能获取到Employee的数据,在backgroundMOC执行完save方法后,自身上下文发生改变的同时,也将改变pushmainMOC中,mainMOC也具备了Employee对象。

因此在backgroundMOCsave方法执行时,是对内存中的上下文作了改变,当拥有PSCmainMOC执行save方法后,是对本地存储区作了改变。

 

六、CoreData-MagicalRecord

CoreData是苹果自家推出的一个持久化框架,使用起来更加面向对象。可是在使用过程当中会出现大量代码,并且CoreData学习曲线比较陡峭,若是掌握很差,在使用过程当中很容易形成其余问题。

国外开发者开源了一个基于CoreData封装的第三方——MagicalRecord,就像是FMDB封装SQLite同样,MagicalRecord封装的CoreData,使得原生的CoreData更加容易使用。而且MagicalRecord下降了CoreData的使用门槛,不用去手动管理以前的PSCMOC等对象。

根据GithubMagicalRecord的官方文档,MagicalRecord的优势主要有三条:

1. 清理项目中CoreData代码
2. 支持清晰、简单、一行式的查询操做
3. 当须要优化请求时,能够获取NSFetchRequest进行修改

添加MagicalRecord到项目中

MagicalRecord添加到项目中,和使用其余第三方同样,能够经过下载源码和CocoaPods两种方式添加。

1.Github下载MagicalRecord源码,将源码直接拖到项目中,后续须要手动更新源码。

2. 也能够经过CocoaPods安装MagicalRecord,须要在Podfile中加入下面命令,后续只须要经过命令来更新。

pod "MagicalRecord"

在以前建立新项目时,经过勾选"Use Core Data"的方式添加CoreData到项目中,会在AppDelegate文件中生成大量CoreData相关代码。若是是大型项目,被占用的位置是很重要的。而对于MagicalRecord来讲,只须要两行代码便可。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     // 初始化CoreData堆栈,也能够指定初始化某个CoreData堆栈
     [MagicalRecord setupCoreDataStack];
     return YES;
 }

 - (void)applicationWillTerminate:(UIApplication *)application {
     // 在应用退出时,应该调用cleanUp方法
     [MagicalRecord cleanUp];
 }

MagicalRecord是支持CoreData.xcdatamodeld文件的,使得CoreData这一优势能够继续使用。创建数据结构时仍是像以前使用CoreData同样,经过.xcdatamodeld文件的方式创建。

支持iCloud

CoreData是支持iCloud的,MagicalRecordiCloud相关的操做也作了封装,只须要使用MagicalRecord+iCloud.h类中提供的方法,就能够进行iCloud相关的操做。

例以下面是MagicalRecord+iCloud.h中的一个方法,须要将相关参数传入便可。

 + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID localStoreNamed:(NSString *)localStore;

建立上下文

MagicalRecord对上下文的管理和建立也比较全面,下面是MagicalRecord提供的部分建立和获取上下文的代码。由于是给NSManagedObjectContext添加的Category,能够直接用NSManagedObjectContext类调用,使用很是方便。

可是须要注意,虽然系统帮咱们管理了上下文对象,对于耗时操做仍然要放在后台线程中处理,而且在主线程中进行UI操做

+ [NSManagedObjectContext MR_context]  设置默认的上下文为它的父级上下文,并发类型为NSPrivateQueueConcurrencyType
+ [NSManagedObjectContext MR_newMainQueueContext]  建立一个新的上下文,并发类型为NSMainQueueConcurrencyType
+ [NSManagedObjectContext MR_newPrivateQueueContext]  建立一个新的上下文,并发类型为NSPrivateQueueConcurrencyType
+ [NSManagedObjectContext MR_contextWithParent:]  建立一个新的上下文,容许自定义父级上下文,并发类型为NSPrivateQueueConcurrencyType
+ [NSManagedObjectContext MR_contextWithStoreCoordinator:]  建立一个新的上下文,并容许自定义持久化存储协调器,并发类型为NSPrivateQueueConcurrencyType
+ [NSManagedObjectContext MR_defaultContext]  获取默认上下文对象,项目中最基础的上下文对象,并发类型是NSMainQueueConcurrencyType

增删改查

MagicalRecordNSManagedObject添加了一个Category,将增删改查等操做放在这个Category中,使得这些操做能够直接被NSManagedObject类及其子类调用。

.1. 增
对于托管模型的建立很是简单,不须要像以前还须要进行上下文的操做,如今这都是MagicalRecord帮咱们完成的。

// 建立并插入到上下文中
 Employee *emp = [Employee MR_createEntity];

.2. 删

 // 从上下文中删除当前对象
 [emp MR_deleteEntity];

.3. 改

 // 获取一个上下文对象
 NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

 // 在当前上下文环境中建立一个新的Employee对象
 Employee *emp = [Employee MR_createEntityInContext:defaultContext];
 emp.name      = @"lxz";
 emp.brithday  = [NSDate date];
 emp.height    = @1.7;

 // 保存修改到当前上下文中
 [defaultContext MR_saveToPersistentStoreAndWait];

.4. 查

 // 执行查找操做,并设置排序条件
 NSArray *empSorted = [Employee MR_findAllSortedBy:@"height" ascending:YES];

自定义NSFetchRequest

下面示例代码中,Employee根据已有的employeeFilter谓词对象,建立了employeeRequest请求对象,并将请求对象作修改后,从MOC中获取请求结果,实现自定义查找条件。

 NSPredicate *employeeFilter = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
 NSFetchRequest *employeeRequest = [Employee MR_requestAllWithPredicate:employeeFilter];
 employeeRequest.fetchOffset = 10;
 employeeRequest.fetchLimit = 10;
 NSArray *employees = [Employee MR_executeFetchRequest:employeeRequest];

参数设置

.1. 能够经过修改MR_LOGGING_DISABLED预编译指令的值,控制log打印。

#define MR_LOGGING_DISABLED 1

.2.MagicalRecordDEBUG模式下,对模型文件发生了更改,而且没有建立新的模型文件版本。MagicalRecord默认会将旧的持久化存储删除,建立新的持久化存储。

MagicalRecord中文文档

MagicalRecord的使用方法还有不少,这里只是将一些比较经常使用的拿出来说讲,其余就不一一讲解了。在Github上有国人翻译的MagicalRecord官方文档,翻译的很是全面,并且是实时更新的。

MagicalRecord中文文档

 

原文连接:https://github.com/DeveloperErenLiu/BlogDemo-CoreData
相关文章
相关标签/搜索