Как мне совместно использовать хранилище Core Data между процессами, использующими NSDistributedNotifications?
Фон
Я уже разместил вопрос об основах совместного использования хранилища базовых данных между процессами.
Я пытаюсь выполнить данные рекомендации и сталкиваюсь с проблемами.
Моя цель
У меня есть два процесса - приложение-помощник и пользовательский интерфейс. Они оба используют одно хранилище данных. Я хочу, чтобы пользовательский интерфейс обновил свой NSManagedObjectContext, когда приложение Helper App сохранит новые данные в хранилище.
Текущий поток программы
Вспомогательный процесс приложения записывает данные в хранилище.
В приложении Helper я слушаю уведомления NSManagedObjectContextDidSaveNotification.
Когда контекст сохранен, я кодирую вставленные, удаленные и обновленные объекты, используя их представления URI и NSArchiver.
Я отправляю NSNotification в NSDistributedNotificationCenter с этим закодированным словарем как userInfo.
Процесс пользовательского интерфейса прослушивает уведомление о сохранении. Когда он получает уведомление, он разархивирует userInfo, используя NSUnarchiver.
Он просматривает все обновленные / вставленные / удаленные объекты из данных URI и заменяет их NSManagedObjects.
Он создает NSNotification с обновленными / вставленными / удаленными объектами.
Я вызываю mergeChangesFromContextDidSaveNotification: в контексте управляемого объекта процесса пользовательского интерфейса, передавая NSNotification, которое я построил на предыдущем шаге.
Эта проблема
Вставленные объекты сбрасываются в штраф в контексте управляемого объекта пользовательского интерфейса, и они появляются в пользовательском интерфейсе. Проблема приходит с обновленными объектами. Они просто не обновляются.
Что я пробовал
Наиболее очевидная вещь, которую стоит попробовать, - это передать уведомление о сохранении из процесса вспомогательного приложения в процесс пользовательского интерфейса. Легко, правда? Ну нет. Распределенные уведомления не позволят мне сделать это, так как словарь userInfo имеет неправильный формат. Вот почему я делаю все, что связано с NSArchiving.
Я пытался вызвать refreshObject:mergeChanges:YES для NSManagedObjects для обновления, но, похоже, это не имеет никакого эффекта.
Я попытался выполнить mergeChangesFromContextDidSaveNotification: селектор в основном потоке и текущем потоке. Ни один из них, кажется, не влияет на результат.
Я попытался использовать mergeChangesFromContextDidSaveNotification: раньше между потоками, что, конечно, намного проще, и он работал отлично. Но мне нужна такая же функциональность между процессами.
Альтернативы?
Я что-то здесь упускаю? У меня постоянно возникает ощущение, что я делаю это намного сложнее, чем нужно, но после прочтения документации несколько раз и потраченных на это нескольких дней, я не вижу другого способа обновить MOC пользовательский интерфейс.
Есть ли более элегантный способ сделать это? Или я просто делаю глупую ошибку где-то в моем коде?
Код
Я пытался сделать его максимально читабельным, но это все еще беспорядок. Сожалею.
Код приложения Helper
-(void)workerThreadObjectContextDidSave:(NSNotification *)saveNotification {
NSMutableDictionary *savedObjectsEncodedURIs = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[saveNotification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// This is the set of updated/inserted/deleted NSManagedObjects.
NSSet *thisSavedObjectSet = [[saveNotification userInfo] objectForKey:thisSavedObjectKey];
NSMutableSet *thisSavedObjectSetEncoded = [NSMutableSet set];
for(id thisSavedObject in [thisSavedObjectSet allObjects]) {
// Construct a set of URIs that will be encoded as NSData
NSURL *thisSavedObjectURI = [[(NSManagedObject *)thisSavedObject objectID] URIRepresentation];
[thisSavedObjectSetEncoded addObject:thisSavedObjectURI];
}
// Archive the set of URIs.
[savedObjectsEncodedURIs setObject:[NSArchiver archivedDataWithRootObject:thisSavedObjectSetEncoded] forKey:thisSavedObjectKey];
}
if ([[savedObjectsEncodedURIs allValues] count] > 0) {
// Tell UI process there are new objects that need merging into it's MOC
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.synapticmishap.lapsus.save" object:@"HelperApp" userInfo:(NSDictionary *)savedObjectsEncodedURIs];
}
}
Код интерфейса
-(void)mergeSavesIntoMOC:(NSNotification *)notification {
NSDictionary *objectsToRefresh = [notification userInfo];
NSMutableDictionary *notificationUserInfo = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[notification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// Iterate through all the URIs in the decoded set. For each URI, get the NSManagedObject and add it to a set.
NSSet *thisSavedObjectSetDecoded = [NSUnarchiver unarchiveObjectWithData:[[notification userInfo] objectForKey:thisSavedObjectKey]];
NSMutableSet *savedManagedObjectSet = [NSMutableSet set];
for(NSURL *thisSavedObjectURI in thisSavedObjectSetDecoded) {
NSManagedObject *thisSavedManagedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:thisSavedObjectURI]];
[savedManagedObjectSet addObject:thisSavedManagedObject];
// If the object is to be updated, refresh the object and merge in changes.
// This doesn't work!
if ([thisSavedObjectKey isEqualToString:NSUpdatedObjectsKey]) {
[managedObjectContext refreshObject:thisSavedManagedObject mergeChanges:YES];
[managedObjectContext save:nil];
}
}
[notificationUserInfo setObject:savedManagedObjectSet forKey:thisSavedObjectKey];
}
// Build a notification suitable for merging changes into MOC.
NSNotification *saveNotification = [NSNotification notificationWithName:@"" object:nil userInfo:(NSDictionary *)notificationUserInfo];
[managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
7 ответов
Вы ищете - (void)refreshObject:(NSManagedObject *) объект mergeChanges:(BOOL) флаг Я считаю.
Это обновит объект информацией в постоянном хранилище, объединяя изменения, если вы хотите.
Я использовал метод в
http://www.mlsite.net/blog/?p=518
тогда каждый объект корректно ошибается, но ошибки выбираются в кеше, поэтому все еще не обновляются
Я должен был сделать [moc stalenessInterval = 0];
И это наконец-то сработало с отношениями.
Это работает, за исключением приложений песочницы. Вы не можете отправить уведомление с информацией о пользователе dict. Вместо этого рассмотрим некоторые другие IPC, такие как XPC или DO.
С другой стороны, использование NSDustributedNotificationCenter не всегда составляет 100%, если система занята.
У меня была точно такая же проблема с приложением для iPhone, над которым я работал. В моем случае решение заключалось в том, чтобы установить для контекста stalenessInterval значение, равное бесконечно малому (например, 0,5 секунды).
Я пошел с предложением Майка и просто смотрел файл магазина на предмет изменений.
Хотя это может быть не самым эффективным, я имел успех, используя - [NSManagedObjectContext reset]
из второго процесса, когда есть изменение в магазине. В моем случае код довольно линейный - все, что я делаю, это запускаю запрос на выборку для некоторых данных после сброса. Я не знаю, как это будет работать с привязками и сложным пользовательским интерфейсом, но, возможно, вы сможете опубликовать уведомление, чтобы вручную обновить информацию, если она не обрабатывается автоматически.
Установка stalenessInterval контекста управляемого объекта работает. Мой случай включает в себя несколько потоков вместо процесса, хотя.
Начиная с iOS 9, теперь вы должны использоватьmergeChangesFromRemoteContextSave:intoContexts:
. Смотрите это для объяснения: https://www.innoq.com/en/blog/ios-writing-core-data-in-today-extension/