【问题标题】:Core Data and threads / Grand Central Dispatch核心数据和线程/ Grand Central Dispatch
【发布时间】:2025-12-18 11:00:01
【问题描述】:

我是 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?

【问题讨论】:

    标签: iphone ios multithreading core-data grand-central-dispatch


    【解决方案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 年前的现在,并且存在一些问题:

        1. 它对 ARC 不友好 - 需要删除对 newMoc 的发布调用 - ARC 甚至无法编译
        2. 您应该在块内进行weakSelf / strongSelf 舞蹈 - 否则您可能会在创建观察者时创建一个保留循环。在此处查看 Apple 的文档:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
        3. @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/

          【讨论】: