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, 0), ^{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]);
}];
// 相当于:串行队列 异步 执行block
dispatch_queue_t queue = dispatch_queue_create("zcp", DISPATCH_QUEUE_SERIAL);  // 只创建一个queue与context绑定,每次都使用这一个queue
dispatch_async(queue, ^{NSLog(@"privateContext block: %@", [NSThread currentThread]);
});// 私有类型上下文执行performBlockAndWait方法
[privateContext performBlockAndWait:^{NSLog(@"privateContext blockAndWait: %@", [NSThread currentThread]);
}];
// 相当于:无队列线程操作,在当前线程中直接执行block
NSLog(@"privateContext blockAndWait: %@", [NSThread currentThread]);

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

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

官方文档


多线程方案

tip:demo在最后

方案一

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

存在的问题

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

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

// 上下文提交保存后的通知name
NSManagedObjectContextDidSaveNotification
// 将通知中上下文提交的信息合并到执行该方法的上下文中
- (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的转换:

// 上下文将要提交保存的通知name
NSManagedObjectContextWillSaveNotification
// 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整理(二)——多线程方案相关推荐

  1. Deep Learning(深度学习)学习笔记整理(二)

    本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流 [1]Deep learning简介 [2]Deep Learning训练过程 [3]Deep Learning模型之: ...

  2. Auto.js 全命令整理(二) 对应用命令专题

    Auto.js 全命令整理(二) 对应用命令专题 目录 Auto.js 全命令整理(二) 对应用命令专题 对应用命令 回顾-应用数据获取 末 对应用命令主要用于确认屏幕显示的是否是正确的页面,so,并 ...

  3. Dynamics CRM和企业微信集成(二)方案实现

    Dynamics CRM和企业微信集成(二)方案实现 准备工作 1. 企业微信 2. CRM准备 3. 其他准备 实施工作 1. 企业微信配置 2. 应用后台处理 1. 登陆时处理 2. WeChat ...

  4. oracle中部门工资降序排列,oracle面试题整理二(10级学员 乔宇整理)

    Oracle面试题整理二(10级学员 乔宇整理) 1.查询工资最高的3 名员工信息 select * from (select * from emp order by sal desc) where ...

  5. 英集芯IP5566带TYPE-C口3A充放快充移动电源5w无线充二合一方案SOC

    一.英集芯Ip5566无线充二合一方案SOC简介: 英集芯Ip5566是一款带TYPE-C口3A充放快充移动电源5w无线充二合一方案SOC,集成升压转换器.锂电池充电管理.电池电量指示.无线充电发射控 ...

  6. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  7. 计算机网路实验二 多线程Web服务器的设计与实现

    计算机网路实验二 多线程Web服务器的设计与实现 一. 实验目的及任务 1.实验目的 熟悉简单网络的搭建与基本配置: 熟悉socket.多线程编程: 熟悉JDK编程工具的基本使用: 熟悉HTTP协议: ...

  8. 微信小程序坐标位置接口使用整理(二)地图接口

    微信小程序坐标位置接口使用整理(二) 微信小程序中实现更多地图服务,可以使用腾讯地图Api. 以下几个场景是使用地图Api调用. 官方Api:微信小程序JavaScript SDK | 腾讯位置服务 ...

  9. Java多线程深入理解学习笔记之二-----多线程实现方案1及方法简介

    多线程的实现方案1: 继承Thread类 重写run()方法 在测试类中使用start()方法 贴一下代码(自定义线程): package textDemo;public class MyThread ...

最新文章

  1. 如何解决Spring Data Maven构建的“生命周期配置未涵盖的插件执行”
  2. python的setting怎么找_Python的Django框架中settings文件的部署建议
  3. 第13章 程序的动态加载和执行(一,引导)
  4. mysql从库延时好高_部署MySQL延迟从库的几个好处
  5. p12解析流程_iOS证书及描述文件制作流程详解
  6. java中int和Integer对比的一些坑
  7. 面试考知识点,吾亦很难通过
  8. 花生壳内网穿透实践指南
  9. 如何理解泊松分布和泊松过程
  10. DirectX11 裁剪像素
  11. 《为什么99%的人不适合做区块链投资》
  12. 05. 路由协议原理
  13. python智能图片识别系统(图片切割、图片识别、区别标识)
  14. Javaweb 聊天室
  15. 大数据周会-本周学习内容总结03
  16. 苹果手机app连不上服务器无响应,iPhone手机软件卡死无响应或无法退出怎么办
  17. WiFi-ESP8266入门开发(五)-HTTP客户端+Yeelink
  18. 固定资产管理软件方案
  19. 计算机音乐数字乐谱核爆神曲,原神乐谱核爆神曲怎么演奏_乐谱核爆神曲_3DM手游...
  20. DNA Sorting(OpenJ_Bailian - 1007)

热门文章

  1. vs2005 c# mysql_在VS2010中怎样用C#创建数据库联接并执行sql语句 最好举个例子讲一下...
  2. python http get 请求_Python:编写HTTP Server处理GET请求
  3. 03 | 事务隔离:为什么你改了我还看不见?笔记(转)
  4. getSystemService
  5. 服务治理之Eureka--基本介绍
  6. Java校招笔试题-Java基础部分(五)
  7. Spring Cloud Eureka 配置原理详解
  8. 来了解一下K8S的Operator模式
  9. @Resource注解使用详解
  10. security中的@EnableGlobalMethodSecurity注解详解