iOS CoreData详解(五)多线程

数据持久化+CoreData 同时被 2 个专栏收录
16 篇文章 0 订阅
75 篇文章 128 订阅

原创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的结构图如下

这其中有几点要注意

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


  • 2
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

黄文臣

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值