22

我在我的应用程序中使用 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_queuefor-saveManagedObjectContext吗?我应该在我NSManagedObject的 in上执行所有操作main_queue吗?一些更新的操作NSManagedObject可能需要 2-3 秒。请指教。

4

3 回答 3

60

对于核心数据,有一条黄金法则——每个线程一个托管对象上下文。托管对象上下文不是线程安全的,因此如果您在后台任务中工作,您可以使用主线程来避免与 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:. 所以像:

/* 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]];
于 2010-11-24T08:34:05.387 回答
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 是为了避免意外创建引用循环。实用积木

于 2010-11-24T08:53:31.383 回答
0

由于 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];
    }
}

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

于 2012-08-20T14:13:23.603 回答