原创blog,转载请注明出处
blog.csdn.net/hello_hwc
欢迎关注我的iOS SDK详解专栏,这里有很多基础的文章
http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html
前言:很多小的App只需要一个ManagedContext在主线程就可以了,但是有时候对于CoreData的操作要耗时很久的,比如App开启的时候要载入大量数据,如果都放在主线程,毫无疑问会阻塞UI造成用户体验很差。通常的方式是,主线程一个ManagedContext处理UI相关的,后台一个线程的ManagedContext负责耗时操作的,操作完成后通知主线程。使用CoreData的并行主要有两种方式
- Notificaiton
- child/parent context
何时会使用到后台-简单来说就是要耗费大量时间,如果在主线程上会影响用户体验的时候。例如
- 导入大量数据
- 执行大量计算
CoreData与线程安全
有一点要时刻记住
CoreData不是线程安全的,对于ManagedObject以及ManagedObjectContext的访问都只能在对应的线程上进行,而不能夸线程。
有几条自己总结的规则
- 对于多个线程,每个线程使用自己独立的ManagedContext
- 对于线程间需要传递ManagedObject的,传递
ManagedObject ID
,通过objectWithID
或者existingObjectWithID
来获取 - 对于持久化存储协调器(NSPersistentStoreCoordinator)来说,可以多个线程共享一个NSPersistentStoreCoordinator
ManagedObjectContext的类型
- NSConfinementConcurrencyType - context使用thread confinement 模式
- NSPrivateQueueConcurrencyType - context在私有线程上的
- NSMainQueueConcurrencyType - context在主线程上
并行的解决方案之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级别即可。设置当主线程只读的时候,都不需要锁。
举例
主线程使用的Context,
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
然后假设有一个BackgroundImport类是一个NSOperation的子类进行相关的后台操作,这里的子类执行是串行的。保存一个主线程属性
@interface BackgroundImport : NSOperation
@property (strong,nonatomic)NSManagedObjectContext * mainContext;
@end
保存一个私有的context
@interface BackgroundImport()
@property (strong,nonatomic)NSManagedObjectContext * privateContext;
@end
然后,在main函数里初始化,并且执行任务,这里一定要在main函数里初始化,因为main函数在创建的新线程上执行。并且注册通知合并变化
-(void)main{
self.privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateContext setPersistentStoreCoordinator:self.mainContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
if (note.object == self.privateContext) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainContext performBlock:^{
[self.mainContext mergeChangesFromContextDidSaveNotification:note];
}];
});
}
}];
//执行耗时的操作
//执行完毕
[self.privateContext performBlock:^{
NSError * error = nil;
if ([self.privateContext hasChanges]) {
[self.privateContext save:&error];
}
}];
}
使用的Demo,注意这里并不是后台线程,只是简单介绍下在实例中的应用
http://download.csdn.net/detail/hello_hwc/8759945
并行的解决方案之child/parent context
ChildContext和ParentContext是相互独立的。只有当ChildContext中调用Save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到
NSPersistentStoreCoordinator
中, parentContext就相当于它的NSPersistentStoreCoordinator。
child/parent context的结构图如下
这其中有几点要注意
- 通常主线程context使用NSMainQueueConcurrencyType,其他线程childContext使用NSPrivateQueueConcurrencyType.
- child和parent的特点是要用Block进行操作,
performBlock
,或者performBlockAndWait
,保证线程安全。这两个函数的区别是performBlock
不会阻塞运行的线程,相当于异步操作,performBlockAndWait
会阻塞运行线程,相当于同步操作。
举例
和上述类似,这次不需要监听变化,因为变化会自动提交到mainContext
self.privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateContext setParentContext:self.mainContext];
//执行耗时的操作
//执行完毕
[self.privateContext performBlock:^{
NSError * error = nil;
if ([self.privateContext hasChanges]) {
[self.privateContext save:&error];
}
}];
Demo下载链接
http://download.csdn.net/detail/hello_hwc/8759969
关于大量数据操作性能差距方面,这篇文章有详细的阐述
性能评估博客
所以,如果对性能要求很高,还是两个独立的context公用一个持久化存储协调器较好