CoreData整理(二)——多线程方案

CoreData整理(二)——多线程方案,第1张

概述CoreData整理(二)——多线程方案 目录 为何使用多线程 如何使用多线程 多线程方案 为何使用多线程     到了这里你一定会问,增删改查功能已经实现了,用的好好的为什么要使用多线程呢?其实想一想,Core Data毕竟是数据持久化技术,如果数据量大的话,使用主线程 *** 作必定会产生线程拥塞。而UI的更新就是在主线程中进行的,这将会导致你的app界面“卡住”。此外当你需要同时执行多个 *** 作时也需要 CoreData整理(二)——多线程方案 目录
为何使用多线程 如何使用多线程 多线程方案
为何使用多线程

    到了这里你一定会问,增删改查功能已经实现了,用的好好的为什么要使用多线程呢?其实想一想,Core Data毕竟是数据持久化技术,如果数据量大的话,使用主线程 *** 作必定会产生线程拥塞。而UI的更新就是在主线程中进行的,这将会导致你的app界面“卡住”。此外当你需要同时执行多个 *** 作时也需要使用多线程。

如何使用多线程

最初想法:
    对于如何去实现,你首先可能会想到的是如下图的方案:实例化一个MOC对象,当有需要执行的 *** 作时就开辟一个线程去执行。但是这样是不行的,由于MOC和MO不是线程安全的,对MO进行的 *** 作和使用MOC进行的 *** 作并不会上锁去保证 *** 作的原子性。如果多线程共用MOC的话会出现数据混乱,甚至更严重的会导致程序崩溃。

例如如下代码,先Add20条数据,再执行Update *** 作。下面的代码在多次频繁执行时会crash。
我们能够简单分析出来,由于MOC是同一个,所以在线程A中的for循环中执行时,有可能线程B已经执行完毕。在这种情况下,线程A中新增的一部分由MOC监听的MO对象会在线程B中被提前Save。这样的情况下两个 *** 作混杂在了一起,严重的会产生crash。

// 线程A执行Add *** 作NSMutableArray *arr = [NSMutableArray array];for (int i = 0; i < 20; i++) {    [arr addobject:@{@"ID": @"111",@"name": @"aaa"}];}dispatch_async(dispatch_get_global_queue(disPATCH_QUEUE_PRIORITY_DEFAulT,0),^{    NSManagedobjectContext *context = self.manager.moc;    int i = 1;    for (NSDictionary *params in arr) {        User *user = [NSEntityDescription insertNewObjectForEntityForname:Entityname inManagedobjectContext:context];        user.userID = params[@"ID"];        user.name   = params[@"name"];        // 模拟在添加了5条数据之后,线程B执行完成Update *** 作        if (i == 5) {            sleep(2);        }        i++;    }    [self.manager saveContext];});// 线程B执行Update *** 作dispatch_async(dispatch_get_global_queue(disPATCH_QUEUE_PRIORITY_DEFAulT,^{    NSManagedobjectContext *context = self.manager.moc;    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityname:Entityname];    NSArray *resultArray = [context executeFetchRequest:fetchRequest error:nil];    for (User *user in resultArray) {        user.name = @"newname";    }    [self.manager saveContext];});

正确的做法:

CoreData不是线程安全的(例子如上),对于Managedobject以及ManagedobjectContext的访问都只能在对应的线程上进行,而不能跨线程。苹果推荐的做法是,一个线程使用一个NSManagedobjectContext对象。由于在每个线程中的context是不同的,而且它只管理自己监听的MO,context之间互不影响,所以不会出现context保存前它所监听的MO被其他context篡改或者提前提交的情况。

API中提供的方法:

NSManagedobjectContext的类型:
实例化时提供了3种类型来方便进行多线程管理:

NSConfinementConcurrencyType(iOS 9废弃)
nsprivateQueueConcurrencyType
NSMainQueueConcurrencyType

NSManagedobjectContext提供的多线程执行方法:
API中提供了多线程执行方法,使得我们不需要去自己维护线程队列或开启线程。

- (voID)performBlock:(voID (^)())block NS_AVAILABLE(10_7,5_0);- (voID)performBlockAnDWait:(voID (^)())block NS_AVAILABLE(10_7,5_0);

1.对于NSConfinementConcurrencyType类型,iOS 9之后过期,context在实例化时并不会自动创建队列,需要自己管理多线程实现并发。当该类型的context使用上述的两个方法时会出现如下的crash。

Terminating app due to uncaught exception 'NSinvalidargumentexception',reason: 'Can only use -performBlock: on an NSManagedobjectContext that was created with a queue.

2.对于nsprivateQueueConcurrencyType类型,该上下文会创建并管理一个私有队列。当你想要异步执行某个 *** 作时,可以在performBlock方法的block中执行。

NSManagedobjectContext *privateContext = [[NSManagedobjectContext alloc] initWithConcurrencyType:nsprivateQueueConcurrencyType];// 私有类型上下文执行performBlock方法[privateContext performBlock:^{    NSLog(@"privateContext block: %@",[NSThread currentThread]);}];// 相当于:串行队列 异步 执行blockdispatch_queue_t queue = dispatch_queue_create("zcp",disPATCH_QUEUE_SERIAL);  // 只创建一个queue与context绑定,每次都使用这一个queuedispatch_async(queue,^{    NSLog(@"privateContext block: %@",[NSThread currentThread]);}); // 私有类型上下文执行performBlockAnDWait方法[privateContext performBlockAnDWait:^{    NSLog(@"privateContext blockwait: %@",[NSThread currentThread]);}];// 相当于:串行队列 同步 执行block(在当前线程中执行)dispatch_queue_t queue = dispatch_queue_create("zcp",disPATCH_QUEUE_SERIAL);  只创建一个queue与context绑定,每次都使用这一个queuedispatch_sync(queue,^{    NSLog(@"privateContext blockwait: %@",[NSThread currentThread]);});

3.对于NSMainQueueConcurrencyType类型,该上下文会关联主队列。如果有UI对象执行的 *** 作或者是需要在主线程中执行的 *** 作,可以使用该类型。

NSManagedobjectContext *mainContext = [[NSManagedobjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];// 主类型上下文执行performBlock方法[mainContext performBlock:^{    NSLog(@"mainContext block: %@",[NSThread currentThread]); }];// 相当于:主队列 异步 执行block dispatch_async(dispatch_get_main_queue(),^{    NSLog(@"mainContext block: %@",[NSThread currentThread]); }); // 主类型上下文执行performBlockAnDWait方法[mainContext performBlockAnDWait:^{    NSLog(@"mainContext blockWait: %@",[NSThread currentThread]);}];// 相当于在主线程中直接执行blockif ([NSThread isMainThread]) {    NSLog(@"mainContext blockWait: %@",[NSThread currentThread]);} else {    dispatch_async(dispatch_get_main_queue(),^{        NSLog(@"mainContext blockWait: %@",[NSThread currentThread]);    });}

官方文档

多线程方案

tip:demo在最后

方案一

使用两个MOC,一个负责在后台处理各种耗时的 *** 作,一个负责与UI进行协作。

存在的问题

我们知道MOC和MO不是线程安全的,为了解决这个问题我们在一个线程中仅使用一个MOC,不能跨线程访问同一个MOC和MO。但是这会存在问题。比如:使用一个context异步执行删除 *** 作,首先查询,在查询出结果时刚好另一个context更新了这些数据,删除 *** 作在之后保存时是不知道数据被修改了,最终会导致删除失败。(该问题的研究,详见Demo中UserDao类的testMergeChanges方法)

为了解决这个问题,我们需要使用通知来监听私有上下文的保存动作,并将更改的信息合并到其他上下文中:

// 上下文提交保存后的通知nameNSManagedobjectContextDIDSaveNotification
// 将通知中上下文提交的信息合并到执行该方法的上下文中- (voID)mergeChangesFromContextDIDSaveNotification:(NSNotification *)notification NS_AVAILABLE(10_5,3_0);
方案二

通过建立上下文间的父子关系,避免上下文的合并 *** 作。

iOS5.0之后新增了MOC之间的父子关系,子上下文的改动保存时会提交给父上下文,最后由根部的上下文提交所有改动给PSC。因此建立关系之后,上下文的改动就不需要用通知去告知其他上下文了。我们可以通过设置如下属性来设置父上下文。

@property (nullable,strong) NSManagedobjectContext *parentContext API_AVAILABLE(macosx(10.7),ios(5.0));

方案二将使用三层的MOC去实现多线程Core Data,privateContext -> mainContext -> rootContext。

其中privateContext用于执行 *** 作,mainContext用于与UI协作,rootContext用于在后台保存所有子上下文的提交。

存在的问题
MO都有唯一的MOID与之对应,为了避免实例化MO时消耗大量资源来确保ID的唯一性,所以MO在实例化时会被给予一个临时的ID,这个ID在MOC范围内唯一。当MOC进行提交时,需要将临时ID转化为全局ID,所以我们需要监听MOC将要保存的通知来处理MOID的转换:

// 上下文将要提交保存的通知nameNSManagedobjectContextwillSaveNotification
// MOID转换方法- (BOol)obtainPermanentIDsForObjects:(NSArray<NSManagedobject *> *)objects error:(NSError **)error NS_AVAILABLE(10_5,3_0);
代码

方案一初始化:

CoreDataManager.m:

方案二初始化:

CoreDataManager.m:


公共辅助方法:

UserDao.m:

增:

删:

改:

查:


后续

CoreData整理(一)——基本概念与简单使用
CoreData整理(三)——MagicalRecord的使用
CoreData整理(四)——数据迁移和其他问题
Demo地址

参考文章

Core Data 线程大揭秘
iOSCoreData详解(五)多线程

总结

以上是内存溢出为你收集整理的CoreData整理(二)——多线程方案全部内容,希望文章能够帮你解决CoreData整理(二)——多线程方案所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1074168.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-26
下一篇2022-05-26

发表评论

登录后才能评论

评论列表(0条)

    保存