核心数据和线程/ Grand Central Dispatch
Posted
技术标签:
【中文标题】核心数据和线程/ Grand Central Dispatch【英文标题】:Core Data and threads / Grand Central Dispatch 【发布时间】:2011-11-24 08:14:53 【问题描述】:我是 Grand Central Dispatch (GCD) 和 Core Data 的初学者,我需要您的帮助才能将 Core Data 与 CGD 一起使用,以便在我向 Core Data 添加 40.000 条记录时 UI 不会被锁定。
我知道CD不是线程安全的,所以我必须使用另一个上下文,然后保存数据并合并上下文,据我从一些文章中了解到。
我还不能做的就是把碎片拼在一起。
所以,在我的代码中,我需要你的帮助来解决这个问题。
我有:
/*some other code*/
for (NSDictionary *memberData in arrayWithResult)
//get the Activities for this member
NSArray *arrayWithMemberActivities = [activitiesDict objectForKey:[memberData objectForKey:@"MemberID"]];
//create the Member, with the NSSet of Activities
[Members createMemberWithDataFromServer:memberData
andActivitiesArray:arrayWithMemberActivities
andStaffArray:nil
andContactsArray:nil
inManagedObjectContext:self.managedObjectContext];
如何将其转换为在后台运行,然后在保存完成后保存数据并更新 UI,而不会在保存 40.000 个对象时阻塞 UI?
【问题讨论】:
【参考方案1】:这是一个很好的示例供您尝试。如果您有任何问题,请随时回来:
self.mainThreadContext... // This is a reference to your main thread context
NSPersistentStoreCoordinator *mainThreadContextStoreCoordinator = [self.mainThreadContext persistentStoreCoordinator];
dispatch_queue_t request_queue = dispatch_queue_create("com.yourapp.DescriptionOfMethod", NULL);
dispatch_async(request_queue, ^
// Create a new managed object context
// Set its persistent store coordinator
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
[newMoc setPersistentStoreCoordinator:mainThreadContextStoreCoordinator]];
// Register for context save changes notification
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
// Do the work
// Your method here
// Call save on context (this will send a save notification and call the method below)
BOOL success = [newMoc save:&error];
if (!success)
// Deal with error
[newMoc release];
);
dispatch_release(request_queue);
并响应上下文保存通知:
- (void)mergeChanges:(NSNotification*)notification
dispatch_async(dispatch_get_main_queue(), ^
[self.mainThreadContext mergeChangesFromContextDidSaveNotification:notification waitUntilDone:YES];
);
完成后台线程上下文后,别忘了从通知中心移除观察者。
[[NSNotificationCenter defaultCenter] removeObserver:self];
【讨论】:
太棒了。谢谢。只是一点点');'在“dispatch_release(request_queue)”之前丢失。谢谢。 在发布新的MOC后我们不应该移除观察者吗? 是的,这听起来是个好主意。我有一个辅助方法,我在其中包装了我的后台处理任务,因此观察者通常会在该类的 dealloc 上被删除。 所以,你的意思是在 dealloc 中我应该像这样删除: [[NSNotificationCenter defaultCenter] removeObserver:self];您可以更新您的答案,以便其他人在查看此内容时清楚吗? @Rog 有更新/更好的方法吗?我使用了你的代码,但我的 UI 仍然处于锁定状态——我也查看了 MagicalRecord,无论我的 UI 被锁定了什么。【参考方案2】:这是一个简单的描述 GCD 和 UI 的 sn-p。您可以将 doWork 替换为执行 CoreData 工作的代码。
关于 CD 和线程安全,GCD 的优点之一是您可以分割应用程序(子系统)的区域以同步并确保它们在同一个队列上执行。您可以在名为 com.yourcompany.appname.dataaccess 的队列上执行所有 CoreData 工作。
在示例中,有一个调用长时间运行的工作的按钮、一个状态标签,我添加了一个滑块以显示我可以在 bg 工作完成时移动滑块。
// on click of button
- (IBAction)doWork:(id)sender
[[self feedbackLabel] setText:@"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^
[self performLongRunningWork];
dispatch_async(main, ^ [self workDone]; );
);
// release queues created.
dispatch_release(queue);
- (void)performLongRunningWork
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
- (void)workDone
[[self feedbackLabel] setText:@"Done ..."];
[[self doWorkButton] setEnabled:YES];
【讨论】:
你的例子很酷,但没有指定核心数据并发。还是谢谢。 关键是,如果您在应用程序中对子系统进行分区,并确保为该子系统排队的所有异步工作都使用相同的队列,队列会处理并发。 来自上面的帖子:“关于 CD 和线程安全,GCD 的优点之一是您可以分割应用程序(子系统)的区域以同步并确保它们在同一个队列上执行. 您可以在名为 com.yourcompany.appname.dataaccess 的队列上执行所有 CoreData 工作。” @bryanmac +1 有关如何获取对 UI 更新主线程的引用的示例。另外不要忘记释放队列,因为您是使用 dispatch_queue_create 自己创建的。 已更新代码中的版本,并发说明已发布。【参考方案3】:这篇博文有关于Core Data并发和示例代码的详细描述: http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
【讨论】:
【参考方案4】:添加另一个您可以检查的信息来源
ThreadedCoreData
Apple 的 ios 开发者库的示例代码,最近更新 (2013-06-09)
演示如何在多线程环境中使用 Core Data, 遵循核心数据中提到的第一个推荐模式 编程指南。
基于 SeismicXML 示例,它下载并解析 RSS 提要 美国地质调查局 (USGS) 提供的数据 最近世界各地的地震。是什么让这个样本与众不同 是它使用核心数据持续存储地震。每一次 您启动应用程序,它会下载新的地震数据,并以 NSOperation 检查重复和新创建的存储 地震作为管理对象。
对于那些刚接触 Core Data 的人来说,比较 SeismicXML 会很有帮助 用这个样品取样并注意必要的成分 在您的应用程序中引入 Core Data。
【讨论】:
【参考方案5】:因此,选择的答案来自近 2 年前的现在,并且存在一些问题:
-
它对 ARC 不友好 - 需要删除对 newMoc 的发布调用 - ARC 甚至无法编译
您应该在块内进行weakSelf / strongSelf 舞蹈 - 否则您可能会在创建观察者时创建一个保留循环。在此处查看 Apple 的文档:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
@RyanG 在评论中询问他为什么要阻止。我的猜测是因为最近编辑的方法有 waitUntilDone:YES - 除了会阻塞主线程。您可能想要 waitUntilDone:NO,但我不知道这些更改事件是否也会触发 UI 更新,因此需要进行测试。
--编辑--
进一步研究#3 - waitUntilDone:YES 不是托管上下文对象的有效方法签名,那么它是如何工作的呢?
【讨论】:
【参考方案6】:比将持久存储协调器附加到新上下文要简单得多,顺便说一句,这也不是线程安全的。
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrency];
[context setParentContext:<main thread context here>];
[context performBlock:^
...
// Execute all code on current context
...
];
NSError *error = nil;
[context save:&error];
if (!error)
[context.parentContext save:&error];
if (error)
NSLog(@"Could not save parent context: %@", error);
else
NSLog(@"Could not save context: %@", error);
关于如何使用多上下文核心数据的精彩教程:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
【讨论】:
以上是关于核心数据和线程/ Grand Central Dispatch的主要内容,如果未能解决你的问题,请参考以下文章
为 Core Data 创建一个仅在一个线程上的 Grand Central Dispatch 队列
暂停和恢复 Grand Central Dispatch 线程
Swift - 多线程实现方式 - Grand Central Dispatch(GCD)