Основные данные, сохраняющие объекты в фоновом режиме
Что я'Я пытаюсь сделать это в двух словах: я использую фоновую очередь для сохранения объектов JSON, извлеченных из веб-службы, в базу данных Core Data Sqlite3. Сохранение происходит в сериализованной фоновой очереди I 'созданный через GCD и сохраненный во вторичный экземпляр NSManagedObjectContext, созданный для этой фоновой очереди. После завершения сохранения мне нужно обновить экземпляр NSManagedObjectContext, который находится в главном потоке, с помощью вновь созданных / обновленных объектов. Однако у меня проблема в том, что экземпляр NSManagedObjectContext в основном потоке не может найти объекты, которые были сохранены в фоновом контексте. Ниже приведен список действий, которые яберу с примерами кода. Любые мысли о том, что яя делаю неправильно?
Создайте фоновую очередь через GCD, запустите всю логику предварительной обработки и затем сохраните фоновый контекст в этом потоке :.// process in the background queue
dispatch_async(backgroundQueue, ^(void){
if (savedObjectIDs.count > 0) {
[savedObjectIDs removeAllObjects];
}
if (savedObjectClass) {
savedObjectClass = nil;
}
// set the thead name
NSThread *currentThread = [NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];
// if there is not already a background context, then create one
if (!_backgroundQueueManagedObjectContext) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
}
// save the JSON dictionary starting at the upper most level of the key path, and return all created/updated objects in an array
NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];
// save the object IDs and the completion block to global variables so we can access them after the save
if (objectIds) {
[savedObjectIDs addObjectsFromArray:objectIds];
}
if (completion) {
saveCompletionBlock = completion;
}
if (managedObjectClass) {
savedObjectClass = managedObjectClass;
}
// save all changes object context
[self saveManagedObjectContext];
});
"saveManagedObjectContext» Метод в основном смотрит, какой поток запущен, и сохраняет соответствующий контекст. Я проверил, что этот метод работает правильно, поэтому я не буду размещать здесь код.
Весь этот код находится в синглтоне и в синглтонеs init метод, я добавляю слушателя для "NSManagedObjectContextDidSaveNotification» и он вызывает метод mergeChangesFromContextDidSaveNotification: метод.
// merge changes from the context did save notification to the main context
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification
{
NSThread *currentThread = [NSThread currentThread];
if ([currentThread.name isEqual:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]) {
// merge changes to the primary context, and wait for the action to complete on the main thread
[_managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
// on the main thread fetch all new data and call the completion block
dispatch_async(dispatch_get_main_queue(), ^{
// get objects from the database
NSMutableArray *objects = [[NSMutableArray alloc] init];
for (id objectID in savedObjectIDs) {
NSError *error;
id object = [_managedObjectContext existingObjectWithID:objectID error:&error];
if (error) {
[self logError:error];
} else if (object) {
[objects addObject:object];
}
}
// remove all saved object IDs from the array
[savedObjectIDs removeAllObjects];
savedObjectClass = nil;
// call the completion block
//completion(objects);
saveCompletionBlock(objects);
// clear the saved completion block
saveCompletionBlock = nil;
});
}
}
Как вы можете видеть в методе выше, я называю "mergeChangesFromContextDidSaveNotification:» в главном потоке, и я установил действие, чтобы ждать, пока не будет сделано. Согласно документации Apple, фоновый поток должен дождаться завершения этого действия, прежде чем он продолжит работу с остальной частью кода ниже этого вызова. Как я упоминал выше, когда я запускаю этот код, кажется, что все работает, но когда я пытаюсь распечатать выбранные объекты на консоли, я неничего не вернуть. Кажется, что слияние на самом деле не происходит или, возможно, не завершается до запуска остальной части моего кода. Есть ли другое уведомление, которое я должен прослушать, чтобы убедиться, что слияние завершено? Или мне нужно сохранить контекст основного объекта после слияния, но до fecth?
Кроме того, я прошу прощения за плохое форматирование кода, но кажется, что SO 'кодовые тегит как определения методов.
Спасибо, парни!
UPDATE: Я
Мы внесли изменения, которые были рекомендованы ниже, но с той же проблемой. Ниже обновленный код у меня есть.
Это код, который вызывает процессы сохранения фонового потока
// process in the background queue
dispatch_async(backgroundQueue, ^(void){
if (savedObjectIDs.count > 0) {
[savedObjectIDs removeAllObjects];
}
if (savedObjectClass) {
savedObjectClass = nil;
}
// set the thead name
NSThread *currentThread = [NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];
// if there is not already a background context, then create one
if (!_backgroundQueueManagedObjectContext) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
}
// save the JSON dictionary starting at the upper most level of the key path
NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];
// save the object IDs and the completion block to global variables so we can access them after the save
if (objectIds) {
[savedObjectIDs addObjectsFromArray:objectIds];
}
if (completion) {
saveCompletionBlock = completion;
}
if (managedObjectClass) {
savedObjectClass = managedObjectClass;
}
// listen for the merge changes from context did save notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
// save all changes object context
[self saveManagedObjectContext];
});
Это код, который вызывается с помощью уведомления NSManagedObjectContextDidSaveNotification
// merge changes from the context did save notification to the main context
- (void)mergeChangesFromBackground:(NSNotification *)notification
{
// kill the listener
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
NSThread *currentThread = [NSThread currentThread];
// merge changes to the primary context, and wait for the action to complete on the main thread
[[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
// dispatch the completion block
dispatch_async(dispatch_get_main_queue(), ^{
// get objects from the database
NSMutableArray *objects = [[NSMutableArray alloc] init];
for (id objectID in savedObjectIDs) {
NSError *error;
id object = [[self managedObjectContext] existingObjectWithID:objectID error:&error];
if (error) {
[self logError:error];
} else if (object) {
[objects addObject:object];
}
}
// remove all saved object IDs from the array
[savedObjectIDs removeAllObjects];
savedObjectClass = nil;
// call the completion block
//completion(objects);
saveCompletionBlock(objects);
// clear the saved completion block
saveCompletionBlock = nil;
});
}
ОБНОВИТЬ:
Итак, я нашел решение. Оказывается, что способ, которым я сохранял идентификаторы объектов в фоновом потоке, а затем пытался использовать их в основном потоке для их повторного извлечения, не былт работает Так что я закончил тем, что извлек вставленные / обновленные объекты из словаря userInfo, который отправляется с уведомлением NSManagedObjectContextDidSaveNotification. Ниже мой обновленный код, который сейчас работает.
Как и прежде, этот код начинает логику предварительного поиска и сохранения
// process in the background queue
dispatch_async(backgroundQueue, ^(void){
// set the thead name
NSThread *currentThread = [NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];
[self logMessage:[NSString stringWithFormat:@"(%@) saveJSONObjects:objectMapping:class:completion:", [managedObjectClass description]]];
// if there is not already a background context, then create one
if (!_backgroundQueueManagedObjectContext) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
}
// save the JSON dictionary starting at the upper most level of the key path
[self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];
// save the object IDs and the completion block to global variables so we can access them after the save
if (completion) {
saveCompletionBlock = completion;
}
// listen for the merge changes from context did save notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
// save all changes object context
[self saveManagedObjectContext];
});
Это модифицированный метод, который обрабатывает NSManagedObjectContextDidSaveNotification
- (void)mergeChangesFromBackground:(NSNotification *)notification
{
// kill the listener
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
// merge changes to the primary context, and wait for the action to complete on the main thread
[[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
// dispatch the completion block
dispatch_async(dispatch_get_main_queue(), ^{
// pull the objects that were saved from the notification so we can get them on the main thread MOC
NSDictionary *userInfo = [notification userInfo];
NSMutableArray *modifiedObjects = [[NSMutableArray alloc] init];
NSSet *insertedObject = (NSSet *)[userInfo objectForKey:@"inserted"];
NSSet *updatedObject = (NSSet *)[userInfo objectForKey:@"updated"];
if (insertedObject && insertedObject.count > 0) {
[modifiedObjects addObjectsFromArray:[insertedObject allObjects]];
}
if (updatedObject && updatedObject.count > 0) {
[modifiedObjects addObjectsFromArray:[updatedObject allObjects]];
}
NSMutableArray *objects = [[NSMutableArray alloc] init];
// iterate through the updated objects and find them in the main thread MOC
for (NSManagedObject *object in modifiedObjects) {
NSError *error;
NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error];
if (error) {
[self logError:error];
}
if (obj) {
[objects addObject:obj];
}
}
modifiedObjects = nil;
// call the completion block
saveCompletionBlock(objects);
// clear the saved completion block
saveCompletionBlock = nil;
});
}