【问题标题】:Grand Central Dispatch (GCD) with CoreData使用 CoreData 的 Grand Central Dispatch (GCD)
【发布时间】:2010-11-24 07:55:02
【问题描述】:

我在我的应用程序中使用 Grand Central Dispatch (GCD) 来完成一些繁重的工作。该应用程序使用 Core-Data 进行数据存储。这是我的场景(以及相关问题):

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);

dispatch_async(request_queue, ^{
    MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

    // … 
    // <heavy lifting>
    // … 

    // … 
    // <update mObject>
    // … 

    [self saveManagedObjectContext];
});     

由于[self saveManagedObjectContext]fetchResultsController 委托方法被自动调用。因此,UI 更新逻辑启动。

现在我的问题是,我需要将main_queue 用于-saveManagedObjectContext 吗?我应该在main_queue 中对我的NSManagedObject 执行所有操作吗?更新NSManagedObject 的某些操作可能需要2-3 秒。请指教。

【问题讨论】:

    标签: iphone core-data objective-c-blocks grand-central-dispatch


    【解决方案1】:

    Core Data 有一条黄金法则——每个线程一个托管对象上下文。托管对象上下文不是线程安全的,因此如果您在后台任务中工作,您可以使用主线程来避免与 UI 操作的线程冲突,或者创建一个新上下文来完成工作。如果工作需要几秒钟后,您应该执行后者以阻止您的 UI 锁定。

    为此,您需要创建一个新上下文并为其提供与主上下文相同的持久存储:

    NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
    [backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
    

    执行您需要执行的任何操作,然后当您保存新上下文时,您需要处理保存通知并将更改与mergeChangesFromContextDidSaveNotification: 消息合并到您的主上下文中。代码应如下所示:

    /* Save notification handler for the background context */
    - (void)backgroundContextDidSave:(NSNotification *)notification {
        /* Make sure we're on the main thread when updating the main context */
        if (![NSThread isMainThread]) {
            [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                                   withObject:notification
                                waitUntilDone:NO];
            return;
        }
    
        /* merge in the changes to the main context */
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
    
    /* ... */
    
    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];
    
    [backgroundContext save:NULL];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:syncContext];
    

    处理保存通知和合并很重要,否则您的主 UI/上下文将看不到您所做的更改。通过合并,您的主 fetchResultsController 等将获得更改事件并按照您的预期更新您的 UI。

    另一个需要注意的重要事情是 NSManagedObject 实例只能在从中获取它们的上下文中使用。如果您的操作需要对对象的引用,那么您必须将对象的objectID 传递给操作并使用existingObjectWithID: 从新上下文中重新获取 NSManagedObject 实例。所以像:

    /* This can only be used in operations on the main context */
    MyNSManagedObject *objectInMainContext =
        [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    
    /* This can now be used in your background context */
    MyNSManagedObject *objectInBackgroundContext =
        (MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];
    

    【讨论】:

    • 那么,你的意思是,在我的情况下,我应该创建一个新的托管对象上下文(backgroundManagedObjectContext),获取所需的托管对象,执行所需的操作,更新托管对象,而不是使用 fetchedResultsController ,保存这个托管对象上下文(backgroundManagedObjectContext),然后合并更改以反映在主托管对象上下文中?这会让我的生活变得非常悲惨。
    • 您仍然可以使用获取的结果控制器来显示和更新 UI 中的内容。我向您展示的代码是在单独的上下文中执行您需要的任何操作所需的全部内容,其他任何内容都不应更改。
    • 这条黄金法则有很多方法:github.com/adam-roth/coredata-threadsafe.
    • 使用 -removeObserver: 你的意思是 'backgroundContext' 而不是 'syncContext' 作为对象:参数吗?如果这是一个愚蠢的问题,我深表歉意,我真的在为线程化核心数据操作而苦苦挣扎!
    • @aroth,在您的 github 存储库中,您声明:“更新您拥有的从 NSManagedObject 派生的每个类,以便它从 IAThreadSafeManagedObject 派生。为此,请执行以下操作:#import (at)interface MyManagedObjectSubclass : IAThreadSafeManagedObject" 这是否意味着必须修改 Xcode 生成的类?
    【解决方案2】:

    您可能知道或已经注意到您必须在主线程上执行 UI 操作。正如您所提到的,它是在您保存 UI 更新时发生的。您可以通过在主线程上嵌套对dispatch_sync 的调用来解决此问题。

    dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
    
    __block __typeof__(self) blockSelf = self;
    
    dispatch_async(request_queue, ^{
        MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    
        // update and heavy lifting...
    
        dispatch_sync(main_queue, ^{
          [blockSelf saveManagedObjectContext];
        });
    });     
    

    使用blockSelf是为了避免意外创建引用循环。 (Practical blocks)

    【讨论】:

      【解决方案3】:

      由于 Core Data 要求每个线程有一个托管对象上下文,一个可能的解决方案是在全局管理器中跟踪每个线程的上下文,然后跟踪保存通知并传播到所有线程:

      假设:

      @property (nonatomic, strong) NSDictionary* threadsDictionary;
      

      以下是获取托管对象的方法(每个线程):

      - (NSManagedObjectContext *) managedObjectContextForThread {
      
      // Per thread, give one back
      NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
      
      NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
      if (existingContext==nil){
          existingContext = [[NSManagedObjectContext alloc] init];
          [existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
          [self.threadsDictionary setValue:existingContext forKey:threadName];
      }
      
      return existingContext;
      

      }

      在您的全局管理器的 init 方法中的某个时刻(我使用了单例):

      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:)                                                    name:NSManagedObjectContextDidSaveNotification                                                   object:nil];
      

      然后接收保存通知并传播到所有其他托管上下文对象:

      - (void)backgroundContextDidSave:(NSNotification *)notification {
          /* Make sure we're on the main thread when updating the main context */
          if (![NSThread isMainThread]) {
              [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                                     withObject:notification
                                  waitUntilDone:NO];
              return;
          }
      
          /* merge in the changes to the main context */
          for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
                  [context mergeChangesFromContextDidSaveNotification:notification];
          }
      }
      

      (为了清楚起见,删除了一些其他方法)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-10
        • 2011-07-10
        • 1970-01-01
        • 2012-05-09
        相关资源
        最近更新 更多