CoreData的结构构成:ios
NSManagedObjectModel的构成:git
能够经过Entity
建立继承自NSManagedObject
类的文件,这个文件就是开发中使用的托管对象,具有模型对象的表示功能,CoreData
的本地持久化都是经过这个类及其子类完成的。github
在CoreData
的总体结构中,主要分为两部分。一个是NSManagedObjectContext
管理的模型部分,管理着全部CoreData
的托管对象。一个是SQLite
实现的本地持久化部分,负责和SQL
数据库进行数据交互,主要由NSPersistentStore
类操做。这就构成了CoreData
的大致结构。正则表达式
从图中能够看出,这两部分都是比较独立的,两部分的交互由一个持久化存储调度器(NSPersistentStoreCoordinator
)来控制。上层NSManagedObjectContext
存储的数据都是交给持久化调度器,由调度器调用具体的持久化存储对象(NSPersistentStore
)来操做对应的数据库文件,NSPersistentStore
负责存储的实现细节。这样就很好的将两部分实现了分离。算法
在模型文件的实体中,参数类型和平时建立继承自NSObject
的模型类大致相似,可是仍是有一些关于类型的说明,下面简单的列举了一下。sql
Undefined: 默认值,参与编译会报错数据库
Integer 16: 整数,表示范围 -32768 ~ 32767
express
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 Value
和Min Value
,经过这两个条件来约定数据,对数据的存储进行一个验证。数值类型都有相同的约定方式,而字符串则是约定长度,date
是约定时间。
Reg. Ex.(Regular Expression
): 能够设置正则表达式,用来验证和控制数据,不对数据自身产生影响。(只能应用于String
类型)
Allows External Storage: 当存储二进制文件时,若是遇到比较大的文件,是否存储在存储区以外。若是选择YES
,存储文件大小超过1MB
的文件,都会存储在存储区以外。不然大型文件存储在存储区内,会形成SQLite
进行表操做时,效率受到影响。
Relationships设置:
delete rule: 定义关联属性的删除规则。在当前对象和其余对象有关联关系时,当前对象被删除后与之关联对象的反应。这个参数有四个枚举值,代码对应着模型文件的相同选项。
NSNoActionDeleteRule 删除后没有任何操做,也不会将关联对象的关联属性指向nil
。删除后使用关联对象的关联属性,可能会致使其余问题。
NSNullifyDeleteRule 删除后会将关联对象的关联属性指向nil
,这是默认值。
NSCascadeDeleteRule 删除当前对象后,会将与之关联的对象也一并删除。
NSDenyDeleteRule 在删除当前对象时,若是当前对象还指向其余关联对象,则当前对象不能被删除。
Type: 主要有两种类型,To One
和To Many
,表示当前关系是一对多仍是一对一。
实体:
Parent Entity: 能够在实体中建立继承关系,在一个实体的菜单栏中经过Parent Entity
能够设置父实体,这样就存在了实体的继承关系,最后建立出来的托管模型类也是具备继承关系的。注意继承关系中属性名不要相同。
使用了这样的继承关系后,系统会将子类继承父类的数据,存在父类的表中,全部继承自同一父类的子类都会将父类部分存放在父类的表中。这样可能会致使父类的表中数据量过多,形成性能问题。
二、CoreData-基础使用
在实体最下面,有一个Fetched Properties
选项,这个选项用的很少,这里就不细讲了。Fetched Properties
用于定义查询操做,和NSFetchRequest
功能相同。定义fetchedProperty
对象后,能够经过NSManagedObjectModel
类的fetchRequestFromTemplateWithName:substitutionVariables:
方法或其余相关方法获取这个fetchedProperty
对象。
获取这个对象后,系统会默认将这个对象缓存到一个字典中,缓存以后也能够经过fetchedProperty
字典获取fetchedProperty
对象。
在模型文件中Entities
下面有一个Fetch Requests
,这个也是配置请求对象的。可是这个使用起来更加直观,能够很容易的完成一些简单的请求配置。相对于上面讲到的Fetched Properties
,这个仍是更方便使用一些。
上面是对Employee
实体的height
属性配置的Fetch Request
,这里配置的height
要小于2米。配置以后能够经过NSManagedObjectModel
类的fetchRequestTemplateForName:
方法获取这个请求对象,参数是这个请求配置的名称,也就是EmployeeFR
。
- (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
中能够经过设置NSFetchRequest
类的predicate
属性,来设置一个NSPredicate
类型的谓词对象当作过滤条件。经过设置这个过滤条件,能够只获取符合过滤条件的托管对象,不会将全部托管对象都加载到内存中。这样是很是节省内存和加快查找速度的,设计一个好的NSPredicate
能够优化CoreData
搜索性能。
[NSPredicate predicateWithFormat:@"age >= 30"]
能够经过NSPredicate
对iOS
中的集合对象执行过滤操做,能够是NSArray
、NSSet
及其子类。对不可变数组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
,也就是设置更深层被匹配的目标。例以下面设置employee
的name
属性为查找条件,就是用点语法设置的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
表中name
为lxz
的数据,而且将全部符合的数据用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
值,也就是执行获取操做后数组中所存储的对象数量。遇到这个需求,若是像以前同样MOC
执行获取操做,获取到数组而后取Count
,这样对内存消耗是很大的。
对于这个需求,苹果提供了两种经常使用的方式获取这个Count
值。这两种获取操做,都是在数据库中完成的,并不须要将托管对象加载到内存中,对内存的开销也是很小的。
/** 获取返回结果的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
对象的resultType
为NSCountResultType
,获取到结果的Count
值。这个枚举值在以前的文章中提到过,除了Count
参数,还能够设置其余三种参数。
- (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
须要先将数据加载到内存中,而后才能对数据进行处理。这样对于大量数据来讲,都加载到内存中是很是消耗内存的,并且容易致使崩溃的发生。若是遇到更改全部数据的某个字段这样的简单需求,须要将相关的托管对象都加载到内存中,而后进行更改、保存。
对于上面这样的问题,CoreData
在iOS8
推出了批量更新API,经过这个API
能够直接在数据库一层就完成更新操做,而不须要将数据加载到内存。除了批量更新操做,在iOS9
中还推出了批量删除API,也是在数据库一层完成的操做。关于批处理的API
不少都是iOS8
、iOS9
出来的,使用时须要注意版本兼容。
可是有个问题,批量更新和批量删除的两个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
,这个并发类型已经被抛弃,会致使崩溃。
在开发过程当中会常常用到UITableView
这样的视图类,这些视图类须要本身管理其数据源,包括网络获取、本地存储都须要写代码进行管理。
而在CoreData
中提供了NSFetchedResultsController
类(fetched results controller
,也叫FRC
),FRC
能够管理UITableView
或UICollectionView
的数据源。这个数据源主要指本地持久化的数据,也能够用这个数据源配合着网络请求数据一块儿使用,主要看业务需求了。
本篇文章会使用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
,或设置一个负责UI
的MOC
,这在后面多线程部分会详细讲解。
在写代码以前,先对以前的模型文件结构作一些修改。
讲FRC
的时候,只须要用到Employee
这一张表,其余表和设置直接忽略。须要在Employee
原有字段的基础上,增长一个String
类型的sectionName
字段,这个字段就是用来存储section title
的,在下面的文章中将会详细讲到。
下面例子是比较经常使用的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
提供了两个方法轻松转换indexPath
和NSManagedObject
的对象,在实际开发中这两个方法很是实用,这也是FRC
和UITableView
、UICollectionView
深度融合的表现。
- (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 <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
会根据传入的字典,自动推断版本迁移的过程。
NSMigratePersistentStoresAutomaticallyOption
设置为YES
,CoreData
会试着把低版本的持久化存储区迁移到最新版本的模型文件。NSInferMappingModelAutomaticallyOption
设置为YES
,CoreData
会试着以最为合理地方式自动推断出源模型文件的实体中,某个属性到底对应于目标模型文件实体中的哪个属性。版本迁移的设置是在建立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];
轻量级迁移方案只是针对增长和改变实体、属性这样的一些简单操做,假设有更复杂的迁移需求,就应该使用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 Mapping
,Attribute Mapping
,Relationship Mapping
)。
在迁移文件的下方是源文件和目标文件的关系。
在上面图中更名后的Employee2
实体并无迁移关系,因为是更名后的实体,系统还不知道实体应该怎样作迁移。因此选中Mapping Model
文件的Employee2 Mappings
,能够看到右侧边栏的Source
为invalid value
。由于要从Employee
实体迁移数据过来,因此将其选择为Employee
,迁移关系就设置完成了。
设置完成后,还应该将以前EmployeeToEmployee
的Mappings
删除,由于这个实体已经被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
是支持多线程的,能够在建立MOC
对象时,指定其并发队列的类型。当指定队列类型后,系统会将操做都放在指定的队列中执行,若是指定的是私有队列,系统会建立一个新的队列。但这都是系统内部的行为,咱们并不能获取这个队列,队列由系统所拥有,并由系统将任务派发到这个队列中执行的。
init
方法初始化上下文,默认就是这个并发类型。这个枚举值是不支持多线程的,从名字上也体现出来了。UI
相关的操做,应该考虑使用这个枚举值初始化上下文。其中NSConfinementConcurrencyType
类型在iOS9
以后已经被苹果废弃,不建议使用这个API
。使用此类型建立的MOC
,调用某些比较新的CoreData
的API
可能会致使崩溃。
在CoreData
中MOC不是线程安全的,在多线程状况下使用MOC
时,不能简单的将MOC
从一个线程中传递到另外一个线程中使用,这并非CoreData
的多线程,并且会出问题。对于MOC
多线程的使用,苹果给出了本身的解决方案。
在建立的MOC
中使用多线程,不管是私有队列仍是主队列,都应该采用下面两种多线程的使用方式,而不是本身手动建立线程。调用下面方法后,系统内部会将任务派发到不一样的队列中执行。能够在不一样的线程中调用MOC
的这两个方法,这个是容许的。
- (void)performBlock:(void (^)())block 异步执行的block,调用以后会马上返回。 - (void)performBlockAndWait:(void (^)())block 同步执行的block,调用以后会等待这个任务完成,才会继续向下执行。
下面是多线程调用的示例代码,在多线程的环境下执行MOC
的save
方法,就是将save
方法放在MOC
的block
体中异步执行,其余方法的调用也是同样的。
[context performBlock:^{
[context save:nil];
}];
可是须要注意的是,这两个block
方法不能在NSConfinementConcurrencyType
类型的MOC
下调用,这个类型的MOC
是不支持多线程的,只支持其余两种并发方式的MOC
。
在业务比较复杂的状况下,须要进行大量数据处理,而且还须要涉及到UI
的操做。对于这种复杂需求,若是都放在主队列中,对性能和界面流畅度都会有很大的影响,致使用户体验很是差,下降屏幕FPS
。对于这种状况,能够采起多个MOC
配合的方式。
CoreData
多线程的发展中,在iOS5
经历了一次比较大的变化,以后能够更方便的使用多线程。从iOS5
开始,支持设置MOC
的parentContext
属性,经过这个属性能够设置MOC
的父MOC
。下面会针对iOS5
以前和以后,分别讲解CoreData
的多线程使用。
尽管如今的开发中早就不兼容iOS5
以前的系统了,可是做为了解这里仍是要讲一下,并且这种同步方式在iOS5
以后也是能够正常使用的,也有不少人还在使用这种同步方式,下面其余章节也是同理。
在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
中定义了下面三个通知:
MOC
将要向存储区存储数据时,调用这个通知。在这个通知中不能获取发生改变相关的NSManagedObject
对象。MOC
向存储区存储数据后,调用这个通知。在这个通知中能够获取改变、添加、删除等信息,以及相关联的NSManagedObject
对象。MOC
中任何一个托管对象发生改变时,调用这个通知。例如修改托管对象的属性。经过监听NSManagedObjectContextDidSaveNotification
通知,获取全部MOC
的save
操做。
[[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
返回值的选项。
NSError
对象来描述错误,而MOC
和持久化存储区不发生改变。MOC
的为准,使用MOC
来覆盖本地存储的冲突部分。MOC
为准,用MOC
的全部NSManagedObject
对象覆盖本地存储的对应对象。MOC
全部的NSManagedObject
对象被本地存储的对应对象所覆盖。上面五种策略中,除了第一个NSErrorMergePolicy
的策略,其余四种中NSMergeByPropertyStoreTrumpMergePolicy
和NSRollbackMergePolicy
,以及NSMergeByPropertyObjectTrumpMergePolicy
和NSOverwriteMergePolicy
看起来是重复的。
其实它们并非冲突的,这四种策略的不一样体如今,对没有发生冲突的部分应该怎么处理。NSMergeByPropertyStoreTrumpMergePolicy
和NSMergeByPropertyObjectTrumpMergePolicy
对没有冲突的部分,未冲突部分数据并不会受到影响。而NSRollbackMergePolicy
和NSOverwriteMergePolicy
则是不管是否冲突,直接所有替换。
题外话:
对于MOC
的这种合并策略来看,有木有感受到CoreData
解决冲突的方式,和SVN
解决冲突的方式特别像。。。
不管是MOC
仍是托管对象,都不该该在其余MOC
的线程中执行操做,这两个API都不是线程安全的。但MOC
能够在其余MOC
线程中调用performBlock:
方法,切换到本身的线程执行操做。
若是其余MOC
想要拿到托管对象,并在本身的队列中使用托管对象,这是不容许的,托管对象是不能直接传递到其余MOC
的线程的。可是能够经过获取NSManagedObject
的NSManagedObjectID
对象,在其余MOC
中经过NSManagedObjectID
对象,从持久化存储区中获取NSManagedObject
对象,这样就是容许的。NSManagedObjectID
是线程安全,而且能够跨线程使用的。
能够经过MOC
获取NSManagedObjectID
对应的NSManagedObject
对象,例以下面几个MOC
的API
。
NSManagedObject *object = [context objectRegisteredForID:objectID]; NSManagedObject *object = [context objectWithID:objectID];
经过NSManagedObject
对象的objectID
属性,获取NSManagedObjectID
类型的objectID
对象。
NSManagedObjectID *objectID = object.objectID;
上面章节中写的大多都是怎么用CoreData
多线程,在掌握多线程的使用后,就能够根据公司业务需求,设计一套CoreData
多线程结构了。对于多线程结构的设计,应该本着尽可能减小主线程压力的角度去设计,将全部耗时操做都放在子线程中执行。
对于具体的设计我根据不一样的业务需求,给出两种设计方案的建议。
在项目中多线程操做比较简单时,能够建立一个主队列mainMOC
,和一个或多个私有队列的backgroundMOC
。将全部backgroundMOC
的parentContext
设置为mainMOC
,采起这样的两层设计通常就可以知足大多数需求了。
将耗时操做都放在backgroundMOC
中执行,mainMOC
负责全部和UI
相关的操做。全部和UI
无关的工做都交给backgroundMOC
,在backgroundMOC
对数据发生改变后,调用save
方法会将改变push
到mainMOC
中,再由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
的parentContext
属性以后,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]; }]; }];
在上面这段代码中,mainMOC
是backgroundMOC
的parentContext
。在backgroundMOC
执行save
方法前,backgroundMOC
和mainMOC
都不能获取到Employee
的数据,在backgroundMOC
执行完save
方法后,自身上下文发生改变的同时,也将改变push
到mainMOC
中,mainMOC
也具备了Employee
对象。
因此在backgroundMOC
的save
方法执行时,是对内存中的上下文作了改变,当拥有PSC
的mainMOC
执行save
方法后,是对本地存储区作了改变。
CoreData
是苹果自家推出的一个持久化框架,使用起来更加面向对象。可是在使用过程当中会出现大量代码,并且CoreData
学习曲线比较陡峭,若是掌握很差,在使用过程当中很容易形成其余问题。
国外开发者开源了一个基于CoreData
封装的第三方——MagicalRecord
,就像是FMDB
封装SQLite
同样,MagicalRecord
封装的CoreData
,使得原生的CoreData
更加容易使用。而且MagicalRecord
下降了CoreData
的使用门槛,不用去手动管理以前的PSC
、MOC
等对象。
根据Github
上MagicalRecord
的官方文档,MagicalRecord
的优势主要有三条:
1. 清理项目中CoreData
代码
2. 支持清晰、简单、一行式的查询操做
3. 当须要优化请求时,能够获取NSFetchRequest
进行修改
将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
文件的方式创建。
CoreData
是支持iCloud
的,MagicalRecord
对iCloud
相关的操做也作了封装,只须要使用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
MagicalRecord
对NSManagedObject
添加了一个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];
下面示例代码中,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.MagicalRecord
在DEBUG
模式下,对模型文件发生了更改,而且没有建立新的模型文件版本。MagicalRecord
默认会将旧的持久化存储删除,建立新的持久化存储。
MagicalRecord
的使用方法还有不少,这里只是将一些比较经常使用的拿出来说讲,其余就不一一讲解了。在Github
上有国人翻译的MagicalRecord
官方文档,翻译的很是全面,并且是实时更新的。