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