Как прервать многопоточность Core Data, чтобы сохранить циклы при объединении изменений?

В моем приложении я использую CoreData для хранения и отображения данных с помощью NSFetchedResultsController.

Я следовал инструкциям Raywenderlich, чтобы сделать это, и в нем много кода - но в целом он работает должным образом - опубликую его части, когда это необходимо. Я застрял на одной проблеме, которую я не могу понять.

Данные, которые отображаются внутри UITableView в сочетании с NSFetchedResultsController, могут быть обновлены в фоновом режиме - и здесь началась моя проблема.

Я делаю Pull-to-refresh подход и начинаю загрузку в фоновом режиме в отдельном потоке. Он использует собственный NSManagedObjectContext, созданный для этого потока, и сохраняет его после создания всех объектов.

Вот код:

- (void)refresh:(id)sender
{
[ServerConnection downloadContactsForToken:token success:^(NSDictionary* responseObject)
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSManagedObjectContext* context = [[[AppManager sharedAppManager] createManagedObjectContext] retain];

            NSArray* responseContacts = responseObject[@"contacts"];
            [responseContacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                //ContactDB is NSManagedObject
                [[[ContactDB alloc] initWithDictionary:obj andContext:context] autorelease];
            }];

            [context save:nil];
            [context release];

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.refreshControl endRefreshing];
            });
        });
    }];
}

Согласно тому, что я прочитал в документации Apple, правильный способ обнаружения этих изменений в основном потоке NSManagedObjectContext заключается в следующем:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

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

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    dispatch_async(dispatch_get_main_queue(), ^{
        if(notification.object != [[AppManager sharedAppManager] managedObjectContext]) {
            [[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
        }
    });
}

В основном, когда я получаю уведомление об изменениях в любом managedObjectContext, я объединяю изменения в контексте основного потока. И в целом это работает, но после того, как я начал профилирование, я обнаружил, что все объекты, которые объединены в описанный процесс, никогда не освобождаются.

Когда я делаю все в главном потоке - это работает - они освобождаются, как и ожидалось, когда я прокручиваю UITableView.

Я нашел обходной путь, и я звоню:

[[[AppManager sharedAppManager] managedObjectContext] reset];

После слияния:

[[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[[[AppManager sharedAppManager] managedObjectContext] reset];

Но я понятия не имею, почему я должен это делать, и если это сломает что-то еще. Возможно, есть лучший способ обновить данные в фоновом режиме, и я нахожусь на совершенно неверном пути.

2 ответа

Решение

В общем, вы не должны делать ничего особенного с управляемыми объектами, кроме обычных правил управления памятью Foundation (подсчет ссылок). Так что просто убедитесь, что вы их нигде не храните, когда они вам не нужны.

Превращение банка объектов в неисправности с помощью -refreshObject:mergeChanges: нужен только тогда, когда вам нужно частично обрезать граф объектов и при этом иметь сильные ссылки на объекты.

И я заметил пару других вещей в вашем коде.

Вы подписываетесь на все уведомления о сохранении контекста. Это опасно, потому что вы можете получать те уведомления для контекстов, которые вам не принадлежат. Например, из адресной книги или из сторонней библиотеки, которую вы используете.

В обработчике завершения сетевых операций вы отправляете работу в глобальную параллельную очередь и создаете оттуда новый контекст. Используя глобальную параллельную очередь, вы не контролируете количество одновременных задач. Возможно, что а) закончатся потоки и б) создаст много новых контекстов, которые будут конкурировать за одного постоянного координатора хранилища и за одно постоянное хранилище. Я хотел бы предложить отправку в последовательную очередь или использование контекста с типом параллелизма частной очереди, который управляет частной последовательной очередью.

Это вызвано сохранением циклов. Очень часто встречается при работе с управляемыми объектами. См. Руководство по программированию основных данных: Управление памятью (разрыв отношений, сохранение циклов).

Когда у вас есть отношения между управляемыми объектами, каждый объект поддерживает строгую ссылку на объект или объекты, с которыми он связан. В среде управляемой памяти это вызывает циклы сохранения (см." Владение и удаление объектов"), которые могут предотвратить освобождение нежелательных объектов. Чтобы гарантировать, что циклы хранения нарушены, когда вы закончите с объектом, вы можете использовать метод контекста управляемого объекта refreshObject:mergeChanges: превратить это в ошибку.

Вы обычно используете refreshObject:mergeChanges: обновить значения свойств управляемого объекта. Если mergeChanges флаг YES метод объединяет значения свойств объекта со значениями объекта, доступными в координаторе постоянного хранилища. Если флаг NO однако метод просто превращает объект в ошибку без слияния, что приводит к освобождению связанных управляемых объектов. Это нарушает цикл сохранения между этим управляемым объектом и другими управляемыми объектами, которые он сохранил.

Также обратите внимание, что в контексте хранятся надежные ссылки на управляемые объекты, которые ожидают изменений (вставок, удалений или обновлений) до тех пор, пока контекст не будет отправлен save:, reset, rollback, или же dealloc сообщение или соответствующее количество отмен, чтобы отменить изменение.

Кроме того, Core Data имеет концепцию под названием "Пользовательское событие". По умолчанию "пользовательское событие" правильно заключено в основной цикл выполнения, однако для MOC, не входящих в основной поток, вы несете ответственность за правильную обработку пользовательских событий. См. Использование ограничения потоков для поддержки параллелизма и отслеживания изменений в других потоках с использованием уведомлений.

Другие вопросы по тегам