22

我在我的应用程序中使用Grand Central Dispatch(GCD)来完成一些繁重工作。该应用程序正在使用Core-Data进行数据存储。这里是我的方案(连同相关的问题):Grand Central Dispatch(GCD)和CoreData

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?我是否应该对NSManagedObjectmain_queue执行所有操作?某些更新NSManagedObject的操作可能需要2-3秒。请指教。

回答

17

正如你可能知道或已经注意到你必须在主线程上执行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

59

当涉及到核心数据 - 每个线程有一个托管对象上下文时,有一条黄金法则。被管对象上下文不是线程安全的,所以如果你在后台任务中工作,你可以使用主线程来避免线程与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]; 

处理保存notifcation和合并是很重要的,否则你的主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]]; 
+1

所以,你说的是,在我的情况下,而不是使用fetchedResultsController,我应该创建一个新的托管对象上下文(backgro如何获取所需的托管对象,执行所需操作,更新托管对象,保存托管对象上下文(backgroundManagedObjectContext),然后合并更改以反映在主托管对象上下文中?这会让我的生活非常痛苦。 – Mustafa 2010-11-24 08:54:07

0

由于核心数据需要一个管理的每个线程对象上下文,一个可能的解决方案来跟踪每线程上下文的全球经理,然后跟踪保存通知,并传播到所有线程:

假设:

@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]; 
    } 
} 

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

相关问题