iOS CoreData详解(五)多线程

时间:2022-10-01 16:23:14

原创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的类型

  1. NSConfinementConcurrencyType - context使用thread confinement 模式
  2. NSPrivateQueueConcurrencyType - context在私有线程上的
  3. 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的结构图如下
iOS CoreData详解(五)多线程

这其中有几点要注意

  • 通常主线程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公用一个持久化存储协调器较好