写在前面的话,首先声明,我这只是对国外一篇博客的总结。因为时间关系,我就不做翻译了,而且我并不觉得译文比原文来的要好,这里附上原文地址,我还是推荐大家看原文http://www.cocoanetics.com/2012/07/multi-context-coredata/
原文讲解了coredata并发操作的两种解决方案,按时间划分为iOS5推出前,以及iOS5后 。
我们先看看coredata正常设置(single-context)的情况,我们在NSManagedObjectContext(MOC),也就是我们俗称的上下文中对进行CURD操作,如果我们执行save操作,moc和NSPersistentStoreCoordinator(PSC)就会保存在disk中,接着我们就可以在VC中展示我们最新的数据。这在大多数情况下,都可以正常进行,但如果我们此时要update大数据的话,save操作将会占据超过1/60秒,那此时VC就会假死甚至导致程序崩溃,显然这不是我们想要的。那我们就需要考虑coredata的并发,这里大家可以先看看官方文档中concurrency with coredata一文。
在iOS5推出之前,我们可以用GCD解决这个问题,当然现在也可以用这种方法
原理就是我们通过监听不同子线程中MOCS的改变,最后把变化整合到主线程的MOC中即可,coredata提供了NSManagedObjectContextDidSaveNotification.这个通知
我们在后台线程中处理大数据
dispatch_async(_backgroudQueue,^{
// create context for backgroud
NSManagedObjectContext *tmpContext = [[NSManaged ObjectContext alloc]init];
tmpContxt.persistentStoreCoordinator = _persistentStoreCoordinator;
// something that takes long
NSError *error;
if(![tmpContext save:&error])
{
//handle error
}
});
搭建简单的Coredata Stack
- (void)_setupCoreDateStack
{
// setup managed object model
NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"Database" withExtension:@"momd"];
_managedObjectModel =[ [NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// setup persistent stroe coordinator
NSURL *storeURL = [NSURL fileURLWithPath:[[NSString cachespath]stringByAppendingPathComponent:Database.db]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:_managedObjectModel];
if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:& ;error]){
//handle error
}
//create MOC
_managedObjectContext =[ [NSManagedObject alloc]init];
[_managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];
//subscribe to change notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
}
主线程的MOC合并变化
- (void)_mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *savedContext = [notification object];
// ignore change notifications for the main MOC
if (_managedObjectContext == savedContext)
{
return;
}
if (_managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
{
// that's another database
return;
}
dispatch_sync(dispatch_get_main_queue(), ^{
[_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
});
}
需要特别注意的是,managedObject是不能在MOCS中传递的,但我们可以通过ObjectID来实现间接传递
NSManagedObjectID *userID = user.objectID;
// make a temporary MOC
dispatch_async(_backgroundQueue, ^{
// create context for background
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
// user for background
TwitterUser *localUser = [tmpContext objectWithID:userID];
// background work
});
在ios5推出之后,增加了initWithConcurrencyType构造方法,我们可以使用Parent/Child Contexts方法来实现coredata的并发
其实和构造子线程创造MOC原理大同小异,只需要注意代码上的差别即可
在这里我们用initWithConcurrencyType方法构建Parent MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
child MOCS保存,parent MOC保存
NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = mainMOC;
[temporaryContext performBlock:^{
// do something that takes some time asynchronously using the temp context
// push to parent
NSError *error;
if (![temporaryContext save:&error])
{
// handle error
}
// save parent to disk asynchronously
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error])
{
// handle error
}
}];
}];
这里需要注意的是每一个MOC需要用performBlock:(async)或者performBlockAndWait:(sync)两个方法以确保在正确的线程中工作,child MOCS不会从parent MOC自动更新,而且这种方法最人性化的一点是你可以设置保存以及取消按钮,点击保存按钮,parent MOC才会从child MOCS获取更新,如果点击取消按钮,你还是可以该干啥干啥,parent MOC是获取不到更新的