Подтвердить что ты не робот

Grand Central Dispatch (GCD) с CoreData

Я использую 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? Должен ли я выполнять все операции на моем NSManagedObject в main_queue? Некоторые из операций, которые обновляют NSManagedObject, могут занимать 2-3 секунды. Просьба сообщить.

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

Предполагая, что:

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

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