<简书 — 刘小壮> http://www.jianshu.com/p/a4710356244dandroid
以前两篇文章都比较偏理论,文字表达比较多一些,但都是干货!学习时先理解理论知识,才能更好的帮助后面的理解。在这篇文章中,将会涉及关于
CoreData
的一些复杂操做,这些操做会涉及分页查询、模糊查询、批处理等高级操做。ios经过这些操做能够更好的使用
CoreData
,提高CoreData
性能。文章中将会出现大量示例代码,经过代码的方式更有助于理解。
文章内容还会比较多,但愿各位耐心看完。git文章中若有疏漏或错误,还请各位及时提出,谢谢!😊github
在iOS
开发过程当中,不少需求都须要用到过滤条件。例如过滤一个集合对象中存储的对象,能够经过Foundation
框架下的NSPredicate
类来执行这个操做。正则表达式
CoreData
中能够经过设置NSFetchRequest
类的predicate
属性,来设置一个NSPredicate
类型的谓词对象当作过滤条件。经过设置这个过滤条件,能够只获取符合过滤条件的托管对象,不会将全部托管对象都加载到内存中。这样是很是节省内存和加快查找速度的,设计一个好的NSPredicate
能够优化CoreData
搜索性能。数据库
NSPredicate
更加偏向于天然语言,不像SQLite
同样有不少固定的语法,看起来也更加清晰易懂。例以下面须要查找条件为年龄30岁以上,而且包括30岁的条件。express
[NSPredicate predicateWithFormat:@"age >= 30"]
能够经过NSPredicate
对iOS
中的集合对象执行过滤操做,能够是NSArray
、NSSet
及其子类。数组
对不可变数组NSArray
执行的过滤,过滤后会返回一个NSArray
类型的结果数组,其中存储着符合过滤条件的对象。缓存
NSArray *results = [array filteredArrayUsingPredicate:predicate]
对可变数组NSMutableArray
执行的过滤条件,过滤后会直接改变原集合对象内部存储的对象,删除不符合条件的对象。安全
[arrayM filterUsingPredicate:predicate]
谓词不仅能够过滤简单条件,还能够过滤复杂条件,设置复合过滤条件。
[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]
固然也能够经过NSCompoundPredicate
对象来设置复合过滤条件,返回结果是一个NSPredicate
的子类NSCompoundPredicate
对象。
[[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]
枚举值NSCompoundPredicateType
参数,能够设置三种复合条件,枚举值很是直观很容易看懂。
下面是列举的一些NSPredicate
的基础语法,这些语法看起来很是容易理解,更复杂的用法能够去看苹果的官方API。
语法 | 做用 |
---|---|
== | 判断是否相等 |
>= | 大于或等于 |
<= | 小于或等于 |
> | 大于 |
< | 小于 |
!= | 不等于 |
AND 或 && | 和 |
OR 或 II | 或 |
NOT 或 ! | 非 |
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"]
在以前的文章中,执行下面MOC
的fetchRequest
方法,通常都须要传入一个NSFetchRequest
类型的参数。这个request
参数能够作一些设置操做,这样就能够以较优的性能获取指定的数据。
- (nullable NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;
在执行fetch
操做前,能够给NSFetchRequest
设置一些参数,这些参数包括谓词、排序等条件,下面是一些基础的设置。
fetchRequestWithEntityName:
或初始化方法来指定表名。NSPredicate
类型的属性,能够设置查找条件,这个属性在开发中用得最多。NSPredicate
能够包括固定格式的条件以及正则表达式。sortDescriptors
属性,能够设置获取结果数组的排序方式,这个属性是一个数组类型,也就是能够设置多种排序条件。(可是注意条件不要冲突)fetchOffset
属性设置从查询结果的第几个开始获取,经过fetchLimit
属性设置每次获取多少个。主要用于分页查询,后面会讲。MOC
执行fetch
操做后,获取的结果是以数组的形式存储的,数组中存储的就是托管对象。NSFetchRequest
提供了参数resultType
,参数类型是一个枚举类型。经过这个参数,能够设置执行fetch
操做后返回的数据类型。
NSManagedObject
的子类,也就是托管对象,这是默认选项。NSManagedObjectID
类型的对象,也就是NSManagedObject
的ID
,对内存占用比较小。MOC
能够经过NSManagedObjectID
对象获取对应的托管对象,而且能够经过缓存NSManagedObjectID
参数来节省内存消耗。count
值,这个操做是发生在数据库层级的,并不须要将数据加载到内存中。// 创建获取数据的请求对象,并指明操做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
值升序的方式排列。
一个模型文件中的不一样实体间,能够设置实体间的关联关系,这个在以前的文章中讲过。实体关联关系分为对一或对多,也能够设置是否双向关联。
这里演示的实体只是简单的To One
的关系,而且下面会给出设置是否双向关联的区别对比。
// 建立托管对象,并将其关联到指定的MOC上 Employee *zsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context]; zsEmployee.name = @"zhangsan"; zsEmployee.height = @1.9f; zsEmployee.brithday = [NSDate date]; Employee *lsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context]; lsEmployee.name = @"lisi"; lsEmployee.height = @1.7f; lsEmployee.brithday = [NSDate date]; Department *iosDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context]; iosDepartment.departName = @"iOS"; iosDepartment.createDate = [NSDate date]; iosDepartment.employee = zsEmployee; Department *androidDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context]; androidDepartment.departName = @"android"; androidDepartment.createDate = [NSDate date]; androidDepartment.employee = lsEmployee; // 执行存储操做 NSError *error = nil; if (context.hasChanges) { [context save:&error]; } // 错误处理 if (error) { NSLog(@"Association Table Add Data Error : %@", error); }
上面建立了四个实体,而且将Employee
都关联到Department
上,完成关联操做后经过MOC
存储到本地。
能够看到上面全部的托管对象建立时,都使用NSEntityDescription
的insert
方法建立,并和上下文创建关系。这时就想问了,我能直接采用传统的init
方法建立吗?
会崩的😱!建立托管对象时须要指定MOC,在运行时动态的生成set
、get
方法。可是直接经过init
方法初始化的对象,系统是不知道这里是须要系统自身生成set
、get
方法的,并且系统也不知道应该对应哪一个MOC
,会致使方法未实现的崩溃。因此就出现了开发中常常出现的错误,以下面崩溃信息:
-[Employee setName:]: unrecognized selector sent to instance 0x7fa665900f60
在上一篇文章中提到过双向关联的概念,也就是设置Relationship
时Inverse
是否为空。下面是Employee
和Department
在数据库中,设置inverse
和没有设置inverse
的两种数据存储,能够很清晰的对比出设置双向关联的区别。
测试代码仍是用上面插入实体的代码,只是更改inverse
选项。
从图中能够看出,未设置双向关联的实体,Department
关联Employee
为属性并存储后,Department
表中的关系是存在的,但Employee
表中的关系依然是空的。而设置双向关联后的实体,在Department
关联Employee
为属性并存储后,Employee
在表中自动设置了和Department
的关系。
双向关联的关系不仅体如今数据库中,在程序运行过程当中托管对象的关联属性,也是随着发生变化的。双向关联的双方,一方的关联属性设置关系后,另外一方关联属性的关系也会发生变化。用下面的代码打印一下各自的关联属性,结果和上面数据库的变化是同样的。
NSLog(@"Department : %@, Employee : %@", androidDepartment.employee, lsEmployee.department);
// 建立获取数据的请求对象,并指明操做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条数据,就能够利用分页查询轻松实现。
// 建立获取数据的请求对象,并指明操做Employee表 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置查找起始点,这里是从搜索结果的第六个开始获取 request.fetchOffset = 6; // 设置分页,每次请求获取六个托管对象 request.fetchLimit = 6; // 设置排序规则,这里设置身高升序排序 NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES]; request.sortDescriptors = @[descriptor]; // 执行查询操做 NSError *error = nil; NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error]; [employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Page Search Result Name : %@, height : %@", obj.name, obj.height); }]; // 错误处理 if (error) { NSLog(@"Page Search Data Error : %@", error); }
上面是一个按照身高升序排序,分页获取搜索结果的例子。查找Employee
表中的实体,将结果按照height
字段升序排序,并从结果的第六个开始查找,而且设置获取的数量也是六个。
有时须要获取具备某些相同特征的数据,这样就须要对查询的结果作模糊匹配。在CoreData
执行模糊匹配时,能够经过NSPredicate
执行这个操做。
// 建立获取数据的请求对象,设置对Employee表进行操做 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 建立模糊查询条件。这里设置的带通配符的查询,查询条件是结果包含lxz NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]; request.predicate = predicate; // 执行查询操做 NSError *error = nil; NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error]; [employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Fuzzy Search Result Name : %@, height : %@", obj.name, obj.height); }]; // 错误处理 if (error) { NSLog(@"Fuzzy Search Data Error : %@", error); }
上面是使用通配符的方式进行模糊查询,NSPredicate
支持多种形式的模糊查询,下面列举一些简单的匹配方式。模糊查询条件对大小写不敏感,因此查询条件大小写都可。
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"lxz"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name ENDSWITH %@", @"lxz"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains %@", @"lxz"];
NSPredicate predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"lxz*"];
在以前的文章中谈到在模型文件中设置请求模板,也就是在.xcdatamodeld
文件中,设置Fetch Requests
,使用时能够经过对应的NSManagedObjectModel
获取设置好的模板。
.... 省略上下文建立步骤 .... // 经过MOC获取模型文件对应的托管对象模型 NSManagedObjectModel *model = context.persistentStoreCoordinator.managedObjectModel; // 经过.xcdatamodeld文件中设置的模板名,获取请求对象 NSFetchRequest *fetchRequest = [model fetchRequestTemplateForName:@"EmployeeFR"]; // 请求数据,下面的操做和普通请求同样 NSError *error = nil; NSArray<Employee *> *dataList = [context executeFetchRequest:fetchRequest error:&error]; [dataList enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Employee.count = %ld, Employee.height = %f", dataList.count, [obj.height floatValue]); }]; // 错误处理 if (error) { NSLog(@"Execute Fetch Request Error : %@", error); }
开发过程当中有时须要只获取所需数据的Count
值,也就是执行获取操做后数组中所存储的对象数量。遇到这个需求,若是像以前同样MOC
执行获取操做,获取到数组而后取Count
,这样对内存消耗是很大的。
对于这个需求,苹果提供了两种经常使用的方式获取这个Count
值。这两种获取操做,都是在数据库中完成的,并不须要将托管对象加载到内存中,对内存的开销也是很小的。
// 设置过滤条件,能够根据需求设置本身的过滤条件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"]; // 建立请求对象,并指明操做Employee表 NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; fetchRequest.predicate = predicate; // 这一步是关键。设置返回结果类型为Count,返回结果为NSNumber类型 fetchRequest.resultType = NSCountResultType; // 执行查询操做,返回的结果仍是数组,数组中只存在一个对象,就是计算出的Count值 NSError *error = nil; NSArray *dataList = [context executeFetchRequest:fetchRequest error:&error]; 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
参数,还能够设置其余三种参数。
// 设置过滤条件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"]; // 建立请求对象,指明操做Employee表 NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; fetchRequest.predicate = predicate; // 经过调用MOC的countForFetchRequest:error:方法,获取请求结果count值,返回结果直接是NSUInteger类型变量 NSError *error = nil; NSUInteger count = [context 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
在执行请求时,是支持对数据进行位运算的。这个操做依然是在数据库层完成的,对内存的占用很是小。
// 建立请求对象,指明操做Employee表 NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置返回值为字典类型,这是为告终果能够经过设置的name名取出,这一步是必须的 fetchRequest.resultType = NSDictionaryResultType; // 建立描述对象 NSExpressionDescription *expressionDes = [[NSExpressionDescription alloc] init]; // 设置描述对象的name,最后结果须要用这个name当作key来取出结果 expressionDes.name = @"sumOperatin"; // 设置返回值类型,根据运算结果设置类型 expressionDes.expressionResultType = NSFloatAttributeType; // 建立具体描述对象,用来描述对那个属性进行什么运算(可执行的运算类型不少,这里描述的是对height属性,作sum运算) NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"height"]]]; // 只能对应一个具体描述对象 expressionDes.expression = expression; // 给请求对象设置描述对象,这里是一个数组类型,也就是能够设置多个描述对象 fetchRequest.propertiesToFetch = @[expressionDes]; // 执行请求,返回值仍是一个数组,数组中只有一个元素,就是存储计算结果的字典 NSError *error = nil; NSArray *resultArr = [context executeFetchRequest:fetchRequest error:&error]; // 经过上面设置的name值,当作请求结果的key取出计算结果 NSNumber *number = resultArr.firstObject[@"sumOperatin"]; NSLog(@"fetch request result is %f", [number floatValue]); // 错误处理 if (error) { NSLog(@"fetch request result error : %@", error); }
从执行结果能够看到,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
,为了保证界面和数据的统一,这一步更新操做更须要作。
// 建立批量更新对象,并指明操做Employee表。 NSBatchUpdateRequest *updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Employee"]; // 设置返回值类型,默认是什么都不返回(NSStatusOnlyResultType),这里设置返回发生改变的对象Count值 updateRequest.resultType = NSUpdatedObjectsCountResultType; // 设置发生改变字段的字典 updateRequest.propertiesToUpdate = @{@"height" : [NSNumber numberWithFloat:5.f]}; // 执行请求后,返回值是一个特定的result对象,经过result的属性获取返回的结果。MOC的这个API是从iOS8出来的,因此须要注意版本兼容。 NSError *error = nil; NSBatchUpdateResult *result = [context executeRequest:updateRequest error:&error]; NSLog(@"batch update count is %ld", [result.result integerValue]); // 错误处理 if (error) { NSLog(@"batch update request result error : %@", error); } // 更新MOC中的托管对象,使MOC和本地持久化区数据同步 [context refreshAllObjects];
上面对Employee
表中全部的托管对象height
值作了批量更新,在更新时经过设置propertiesToUpdate
字典来控制更新字段和更新的值,设置格式是字段名 : 新值
。经过设置批处理对象的predicate
属性,设置一个谓词对象来控制受影响的对象。
还能够对多个存储区(数据库)作一样批处理操做,经过设置其父类的affectedStores
属性,类型是一个数组,能够包含受影响的存储区,多个存储区的操做对批量删除一样适用。
MOC
在执行请求方法时,发现方法名也不同了,执行的是executeRequest: error:
方法,这个方法是从iOS8
以后出来的。方法传入的参数是NSBatchUpdateRequest
类,此类并非继承自NSFetchRequest
类,而是直接继承自NSPersistentStoreRequest
,和NSFetchRequest
是平级关系。
// 建立请求对象,并指明对Employee表作操做 NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 经过谓词设置过滤条件,设置条件为height小于1.7 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < %f", 1.7f]; fetchRequest.predicate = predicate; // 建立批量删除请求,并使用上面建立的请求对象当作参数进行初始化 NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest]; // 设置请求结果类型,设置为受影响对象的Count deleteRequest.resultType = NSBatchDeleteResultTypeCount; // 使用NSBatchDeleteResult对象来接受返回结果,经过id类型的属性result获取结果 NSError *error = nil; NSBatchDeleteResult *result = [context 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和本地持久化区数据同步 [context refreshAllObjects];
大多数状况下,涉及到托管对象的操做,都须要将其加载到内存中完成。因此使用CoreData
时,须要注意内存的使用,不要在内存中存在过多的托管对象。在已经作系统兼容的状况下,进行大量数据的操做时,应该尽可能使用批处理来完成操做。
须要注意的是,refreshAllObjects
是从iOS9
出来的,在iOS9
以前由于要作版本兼容,因此须要使用refreshObject: mergeChanges:
方法更新托管对象。
// 建立请求对象,并指明操做Employee表 NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 建立异步请求对象,并经过一个block进行回调,返回结果是一个NSAsynchronousFetchResult类型参数 NSAsynchronousFetchRequest *asycFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult * _Nonnull result) { [result.finalResult enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"fetch request result Employee.count = %ld, Employee.name = %@", result.finalResult.count, obj.name); }]; }]; // 执行异步请求,和批量处理执行同一个请求方法 NSError *error = nil; [context executeRequest:asycFetchRequest error:&error]; // 错误处理 if (error) { NSLog(@"fetch request result error : %@", error); }
上面经过NSAsynchronousFetchRequest
对象建立了一个异步请求,并经过block
进行回调。若是有多个请求同时发起,不须要担忧线程安全的问题,系统会将全部的异步请求添加到一个操做队列中,在前一个任务访问数据库时,CoreData
会将数据库加锁,等前面的执行完成才会继续执行后面的操做。
NSAsynchronousFetchRequest
提供了cancel
方法,也就是能够在请求过程当中,将这个请求取消。还能够经过一个NSProgress
类型的属性,获取请求完成进度。NSAsynchronousFetchRequest
类从iOS8
开始可使用,因此低版本须要作版本兼容。
须要注意的是,执行请求时MOC
并发类型不能是NSConfinementConcurrencyType
,这个并发类型已经被抛弃,会致使崩溃。
好多同窗都问我有Demo
没有,其实文章中贴出的代码组合起来就是个Demo
。后来想了想,仍是给本系列文章配了一个简单的Demo
,方便你们运行调试,后续会给全部博客的文章都加上Demo
。
Demo
只是来辅助读者更好的理解文章中的内容,应该博客结合Demo
一块儿学习,只看Demo
仍是不能理解更深层的原理。Demo
中几乎每一行代码都会有注释,各位能够打断点跟着Demo
执行流程走一遍,看看各个阶段变量的值。
Demo地址:刘小壮的Github
这两天更新了一下文章,将CoreData
系列的六篇文章整合在一块儿,作了一个PDF
版的《CoreData Book》,放在我Github上了。PDF
上有文章目录,方便阅读。
若是你以为不错,请把PDF帮忙转到其余群里,或者你的朋友,让更多的人了解CoreData,衷心感谢!😁