Grand Central Dispatch (GCD) с CoreData - PullRequest
       45

Grand Central Dispatch (GCD) с CoreData

22 голосов
/ 24 ноября 2010

Я использую 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 методы делегата вызываются автоматически. Следовательно, логика обновления интерфейса запускается.

Теперь мой вопрос: нужно ли использовать main_queue для -saveManagedObjectContext? Должен ли я выполнять все операции на моем NSManagedObject in main_queue? Некоторые операции по обновлению NSManagedObject могут занять 2-3 секунды. Пожалуйста, сообщите.

Ответы [ 3 ]

60 голосов
/ 24 ноября 2010

Для базовых данных существует золотое правило - один контекст управляемого объекта на поток. Контексты управляемых объектов не являются потокобезопасными, поэтому, если вы выполняете работу в фоновой задаче, вы либо используете основной поток, чтобы избежать конфликтов потоков с операциями пользовательского интерфейса, либо создаете новый контекст для выполнения работы. Если работа будет выполняться Через несколько секунд вы должны сделать это, чтобы остановить блокировку вашего интерфейса.

Для этого вы создаете новый контекст и сохраняете в нем то же постоянное хранилище, что и ваш основной контекст:

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

Обработка уведомлений о сохранении и слиянии важна, в противном случае ваш основной пользовательский интерфейс / контекст не увидит сделанные вами изменения. При слиянии ваш основной fetchResultsController и т. Д. Получит события изменений и обновит ваш пользовательский интерфейс, как вы ожидаете.

Еще одна важная вещь, которую следует отметить, - это то, что экземпляры NSManagedObject могут использоваться только в том контексте, из которого они были получены. Если вашей операции нужна ссылка на объект, вы должны передать объекту objectID операции и повторно извлечь экземпляр NSManagedObject из нового контекста, используя 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]];
17 голосов
/ 24 ноября 2010

Как вы, вероятно, знаете или заметили, вы должны выполнять операции пользовательского интерфейса в основном потоке.Как вы упоминаете, когда вы сохраняете пользовательский интерфейс, происходит обновление.Вы можете решить эту проблему, вложив вызов в 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 позволяет избежать случайного создания циклов ссылок.1009 * ( Практические блоки )

0 голосов
/ 20 августа 2012

Поскольку для базовых данных требуется один контекст управляемого объекта на поток, возможным решением было бы отслеживать контекст для каждого потока в глобальном менеджере, а затем отслеживать уведомления о сохранении и распространять на все потоки:

Предполагая:

@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 вашего глобального менеджера (я использовалsingleton):

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

(некоторые другие методы были удалены для ясности)

...