CoreData整理(二)——多线程方案
CoreData整理(二)——多线程方案目录
为何使用多线程????到了这里你一定会问,增删改查功能已经实现了,用的好好的为什么要使用多线程呢?其实想一想,Core Data毕竟是数据持久化技术,如果数据量大的话,使用主线程操作必定会产生线程拥塞。而UI的更新就是在主线程中进行的,这将会导致你的app界面“卡住”。此外当你需要同时执行多个操作时也需要使用多线程。 如何使用多线程最初想法: 例如如下代码,先Add20条数据,再执行Update操作。下面的代码在多次频繁执行时会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的类型: NSConfinementConcurrencyType(iOS 9废弃) NSManagedObjectContext提供的多线程执行方法: - (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 blockwait: %@",[NSThread currentThread]); }]; // 相当于:串行队列 同步 执行block(在当前线程中执行) dispatch_queue_t queue = dispatch_queue_create("zcp",DISPATCH_QUEUE_SERIAL); 只创建一个queue与context绑定,每次都使用这一个queue dispatch_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]); }]; // 相当于在主线程中直接执行block if ([NSThread isMainThread]) { NSLog(@"mainContext blockWait: %@",[NSThread currentThread]); } else { dispatch_async(dispatch_get_main_queue(),^{ NSLog(@"mainContext blockWait: %@",[NSThread currentThread]); }); } 官方文档 多线程方案
方案一使用两个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用于在后台保存所有子上下文的提交。 存在的问题 // 上下文将要提交保存的通知name NSManagedObjectContextWillSaveNotification // MOID转换方法 - (BOOL)obtainPermanentIDsForObjects:(NSArray<NSManagedObject *> *)objects error:(NSError **)error NS_AVAILABLE(10_5,3_0); 代码方案一初始化: CoreDataManager.m: 方案二初始化: CoreDataManager.m: 公共辅助方法: UserDao.m: 增: 删: 改: 查: 后续CoreData整理(一)——基本概念与简单使用 参考文章Core Data 线程大揭秘 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |