前言:很多小的App只需要一个ManagedContext在主线程就可以了,但是有时候对于CoreData的操作要耗时很久的,比如App开启的时候要载入大量数据,如果都放在主线程,毫无疑问会阻塞UI造成用户体验很差。通常的方式是,主线程一个ManagedContext处理UI相关的,后台一个线程的ManagedContext负责耗时操作的,操作完成后通知主线程。使用CoreData的并行主要有两种方式
Notification child/parent context
何时会使用到后台-简单来说就是要耗费大量时间,如果在主线程上会影响用户体验的时候。
例如:导入大量数据 执行大量计算
CoreData与线程安全
CoreData不是线程安全的,对于ManagedObject以及ManagedObjectContext的访问都只能在对应的线程上进行,而不能跨线程。
有几条自己总结的规则:
对于多个线程,每个线程使用自己独立的ManagedContext 对于线程间需要传递ManagedObject的,传递ManagedObject ID,通过objectWithID或者existingObjectWithID来获取 对于持久化存储协调器(NSPersistentStoreCoordinator)来说,可以多个线程共享一个NSPersistentStoreCoordinator
CoreData几个对象的介绍如下:
具体实现之前先来认识如下几个对象
(1)NSManagedObjectModel(被管理的对象模型)
相当于实体,不过它包含 了实体间的关系
(2)NSManagedObjectContext(被管理的对象上下文)
操作实际内容
作用:插入数据 查询 更新 删除
(3)NSPersistentStoreCoordinator(持久化存储助理)
相当于数据库的连接器
(4)NSFetchRequest(获取数据的请求)
相当于查询语句
(5)NSPredicate(相当于查询条件)
(6)NSEntityDescription(实体结构)
为了方便实现,本文整理一个数据管理类来测试CoreData:CoreDataManager
CoreDataManager.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface CoreDataManager : NSObject<NSCopying>
@property(strong,nonatomic,readonly)NSManagedObjectModel* managedObjectModel;//管理数据模型
@property(strong,nonatomic,readonly)NSManagedObjectContext* managedObjectContext;//管理数据内容
@property(strong,nonatomic,readonly)NSPersistentStoreCoordinator* persistentStoreCoordinator;//持久化数据助理
//创建数据库管理者单例
+(instancetype)shareManager;
//插入数据
-(void)insertData:(NSString*)tempName;
//删除数据
-(void)deleteData;
//删除数据
-(void)deleteData:(NSString*)tempName;
//查询数据
-(void)queryData;
//根据条件查询
-(void)queryData:(NSString*)tempName;
//更新数据
-(void)updateData:(NSString*)tempName;
@end
CoreDataManager.m
#import "CoreDataManager.h"
#import "Car.h"
static CoreDataManager *shareManager=nil;
@implementation CoreDataManager
@synthesize managedObjectContext =_managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
//实例化对象
-(instancetype)init
{
self=[super init];
if (self) {
}
return self;
}
//创建数据库管理者单例
+(instancetype)shareManager
{
//这里用到了双重锁定检查
if(shareManager==nil){
@synchronized(self){
if(shareManager==nil){
shareManager =[[[self class]alloc]init];
}
}
}
return shareManager;
}
-(id)copyWithZone:(NSZone *)zone
{
return shareManager;
}
+(id)allocWithZone:(struct _NSZone *)zone
{
if(shareManager==nil){
shareManager =[super allocWithZone:zone];
}
return shareManager;
}
//托管对象
-(NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel!=nil) {
return _managedObjectModel;
}
NSURL* modelURL=[[NSBundle mainBundle] URLForResource:@"myCoreData" withExtension:@"momd"];
_managedObjectModel=[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
//托管对象上下文
-(NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext!=nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator* coordinator=[self persistentStoreCoordinator];
if (coordinator!=nil) {
_managedObjectContext=[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];//NSMainQueueConcurrencyType NSPrivateQueueConcurrencyType
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
//持久化存储协调器
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator!=nil) {
return _persistentStoreCoordinator;
}
NSString* docs=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSURL* storeURL=[NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"myCoreData.sqlite"]];
NSLog(@"path is %@",storeURL);
NSError* error=nil;
_persistentStoreCoordinator=[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Error: %@,%@",error,[error userInfo]);
}
return _persistentStoreCoordinator;
}
//插入数据
-(void)insertData:(NSString*)tempName
{
//读取类
Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
car.name=tempName;
//保存
NSError *error;
[self.managedObjectContext save:&error];
}
//删除数据
-(void)deleteData
{
//创建读取类
NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
//创建连接
NSFetchRequest* request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
//启动查询
NSError *error;
NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
if(deleteArr.count){
for (Car *car in deleteArr) {
[self.managedObjectContext deleteObject:car];
}
NSError *error;
[self.managedObjectContext save:&error];
}else{
NSLog(@"未查询到可以删除的数据");
}
}
//删除数据
-(void)deleteData:(NSString*)tempName;
{
//创建读取类
NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
//创建连接
NSFetchRequest* request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
//创建检索条件
NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
[request setPredicate:predicate];
//启动查询
NSError *error;
NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
if(deleteArr.count){
for (Car *car in deleteArr) {
[self.managedObjectContext deleteObject:car];
}
NSError *error;
[self.managedObjectContext save:&error];
}else{
NSLog(@"未查询到可以删除的数据");
}
}
//查询数据
-(void)queryData
{
//创建读取类
NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
//创建连接
NSFetchRequest* request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
//启动查询
NSError *error;
NSArray *carArr=[self.managedObjectContext executeFetchRequest:request error:&error];
for(Car *car in carArr){
NSLog(@"car---->%@",car.name);
}
}
-(void)queryData:(NSString*)tempName
{
//创建读取类
NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
//创建连接
NSFetchRequest* request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
//创建检索条件
NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
[request setPredicate:predicate];
//启动查询
NSError *error;
NSArray *carArr=[self.managedObjectContext executeFetchRequest:request error:&error];
for(Car *car in carArr){
NSLog(@"car---->%@",car.name);
}
}
//更新数据
-(void)updateData:(NSString*)tempName
{
//创建读取类
NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
//创建连接
NSFetchRequest* request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
//创建检索条件
NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
[request setPredicate:predicate];
//启动查询
NSError *error;
NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
if(deleteArr.count){
for (Car *car in deleteArr) {
car.name=@"test";
}
NSError *error;
[self.managedObjectContext save:&error];
}else{
NSLog(@"未查询到可以删除的数据");
}
}
@end
测试一下效率:测试数据10000条
NSMutableArray *testArray =[[NSMutableArray alloc]init];
int testMaxCount =10000;
for(int i=0;i<testMaxCount;i++){
NSString *string = [[NSString alloc] initWithFormat:@"%d",i];
[testArray addObject:string];
}
//测试一下效率 第1种
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for(NSString *tempName in testArray){
[[CoreDataManager shareManager]insertData:tempName];
}
CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
NSLog(@"coreData数据插入 time cost: %0.3f", end - start);
//测试一下效率 第2种
start = CFAbsoluteTimeGetCurrent();
[[CoreDataManager shareManager]insertDatas:testArray];
end=CFAbsoluteTimeGetCurrent();
NSLog(@"coreData数据插入 time cost: %0.3f", end - start);
insertData函数:
//插入数据
-(void)insertData:(NSString*)tempName
{
//读取类
Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
car.name=tempName;
//保存
NSError *error;
[self.managedObjectContext save:&error];
}
insertDatas函数:
//插入数据
-(void)insertDatas:(NSArray*)tempNames
{
for(NSString *name in tempNames){
Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
car.name=name;
}
NSError *error;
[self.managedObjectContext save:&error];
}
**运行结果:
第一种:8.408
第二种:0.162
但是有个超级大的问题,
第二种方式虽然效率高,但是插入数据乱序。
第一种正常但是效率超低,同样近似的数据量sqlite效率比这个高不知多少倍。
Coredata批量操作支持的不太好。
另外:CoreData 的数据模型升级兼容性比较差,如果模型不对,会导致程序连起都起不来。虽然提供了模型升级代码,但是在客户端的管理模型版本管理也会相对复杂。**
并行的解决方案之Notification
简单来说,就是不同的线程使用不同的context进行操作,当一个线程的context发生变化后,利用notification来通知另一个线程Context,另一个线程调用mergeChangesFromContextDidSaveNotification来合并变化。
Notification的种类
NSManagedObjectContextObjectsDidChangeNotification 当Context中的变量改变时候触发。
NSManagedObjectContextDidSaveNotification 在一个context调用save完成以后触发。注意,这些managed object只能在当前线程使用,如果在另一个线程响应通知,要调用mergeChangesFromContextDidSaveNotification来合并变化。
NSManagedObjectContextWillSaveNotification。将要save。
一种比较好的iOS模式就是使用一个NSPersistentStoreCoordinator,以及两个独立的Contexts,一个context负责主线程与UI协作,一个context在后台负责耗时的处理。
为什么说这样做的效率更高?
这样做两个context共享一个持久化存储缓存,而且这么做互斥锁只需要在sqlite级别即可。设置当主线程只读的时候,都不需要锁。
并行的解决方案之child/parent context
ChildContext和ParentContext是相互独立的。只有当ChildContext中调用Save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到NSPersistentStoreCoordinator中, parentContext就相当于它的NSPersistentStoreCoordinator。
这其中有几点要注意
通常主线程context使用NSMainQueueConcurrencyType,其他线程childContext使用NSPrivateQueueConcurrencyType. child和parent的特点是要用Block进行操作,performBlock,或者performBlockAndWait,保证线程安全。
这两个函数的区别是performBlock不会阻塞运行的线程,相当于异步操作,performBlockAndWait会阻塞运行线程,相当于同步操作。
举例
和上述类似,这次不需要监听变化,因为变化会自动提交到mainContext。
CoreData线程安全问题
NSManagedObjectContext不是线程安全的,只能在创建NSManagedObjectContext的那个线程里访问它。一个数据库有多个UIManagedDocument和context,它们可以在不同的线程里创建,只要能管理好它们之间的关系就没问题。
线程安全的意思是,程序可能会崩溃,如果多路访问同一个NSManagedObjectContext,或在非创建实例的线程里访问实例,app就会崩溃。对此要怎么做呢?NSManagedObjectContext有个方法叫performBlock可以解决这个问题:
[context performBlock:^{ //or performBlockAndWait:
// do stuff with context
}];
它会自动确保block里的东西都在正确的context线程里执行,但这不一定就意味着使用了多线程。事实上,如果在主线程下创建的context,那么这个block会回到主线程来,而不是在其他线程里运行,这个performBlock只是确保block运行在正确的线程里。
NSManagedObjectContext,包括所有使用SQL的Core Data,都有一个parentContext,这就像是另一个NSManagedObjectContext,在真正写入数据库之前要写入到这里。可以获取到parentContext,可以让parentContext调用performBlock来做一些事,这总是在另一个线程里进行。parentContext和创建的NSManagedObjectContext不在一个线程里运行,可以通过performBlock在那个线程里执行你要做的事。记住,如果改变了parentContext,必须保存,然后重新获取child context。如果你想在非主线程载入很多内容,那么就全部放入数据库,然后在主线程去获取,这个效率非常快。