НАСТРОЙКА (Вы можете прочитать это позже и сначала перейти к разделу сценариев)
Это старое приложение с ручной настройкой стека CoreData следующим образом:
+ (NSManagedObjectContext *)masterManagedObjectContext
{
if (_masterManagedObjectContext) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_masterManagedObjectContext.retainsRegisteredObjects = YES;
_masterManagedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_masterManagedObjectContext.persistentStoreCoordinator = coordinator;
}
return _masterManagedObjectContext;
}
+ (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext) {
return _managedObjectContext;
}
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.retainsRegisteredObjects = YES;
_managedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_managedObjectContext.parentContext = masterContext;
}
return _managedObjectContext;
}
+ (NSManagedObjectContext *)newManagedObjectContext
{
__block NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *parentContext = [self managedObjectContext];
if (parentContext) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
newContext.parentContext = parentContext;
}
return newContext;
}
А затем рекурсивно сохраните контекст:
+ (void)saveContext:(NSManagedObjectContext *)context
{
[context performBlockAndWait:^{
if (context.hasChanges && context.persistentStoreCoordinator.persistentStores.count) {
NSError *error = nil;
if ([context save:&error]) {
NSLog(@"saved context: %@", context);
// Recursive save parent context.
if (context.parentContext) [self saveContext:context.parentContext];
}
else {
// do some real error handling
NSLog(@"Could not save master context due to %@", error);
}
}
}];
}
СЦЕНАРИЙ
Приложение загружает много данных с сервера, затем сначала выполняет обновление внутри newContext
, а затем объединяется с mainContext
-> masterContext
-> persistentStore
.
Из-за большого количества данных процесс синхронизации был разделен примерно на 10 асинхронных потоков => у нас есть 10 newContext
одновременно.
Теперь данные сложные, с такими вещами, как parents <-> children (same class)
. 1 parent
может иметь много children
, а child
может иметь mother, father, god father, step mother...
, поэтому получается n-n relationship
. Сначала мы извлекаем parent
, затем выполняем выборку child
, затем устанавливаем child
в parent
и так далее.
Сервер какой-то глупый, он не может отправлять отключенные объекты. Однако клиент хотел бы контролировать отображение объектов приложения из серверной части, поэтому у меня есть 2 свойства для этого:
hasUpdated
: В начале процесса загрузки выполните пакетное обновление, установите для всех объектовhasUpdated
значение НЕТ. Получив данные с сервера, обновите это свойство до YES.isActive
: Когда вся загрузка завершена, выполните пакетное обновление этого свойства до NO, еслиhasUpdate == NO
. Затем у меня есть фильтр, который не будет отображать объект сisActive == NO
ПРОБЛЕМА
Клиенты жалуются, почему некоторые объекты отсутствуют, даже если они включены в серверной части. Я так долго боролся и отлаживал после того, как добрался до этой странной проблемы:
- newContext.updatedObjects: {obj1.ID = 100,
hasUpdated == YES
} - "сохраненный новый контекст"
- mainContext.updatedObjects: {obj1.ID = 100,
hasUpdated == NO
}
// Я остановлюсь здесь. Очевидно, что master был обновлен = NO, и, наконец, isActive
будет установлено значение no, что приведет к отсутствию объектов.
Если это происходило каждый раз, то наверное проще исправить (¿может быть?). Однако это происходит так:
- Первый запуск (под первым я имею в виду запуск приложения, откуда был вызван
appDidFinishLaunch...
): все правильно - 2-й раз: отсутствует (153 объекта)
- 3-й раз: все правильно
- 4-й раз: отсутствует (153 объекта) (опять же? именно те, у которых несколько родителей, я так думаю!)
- 5-й раз: снова правильно
- ... so on.
Кроме того, похоже, что это произошло для объектов, которые имеют одинаковый контекст (один и тот же newContext
). Невероятный.
ВОПРОСЫ
Почему это происходит? Как это исправить? Если бы у этих объектов не было детей, моя жизнь была бы проще!!!!
БОНУС
Если вы хотите узнать, как происходит пакетное обновление, оно приведено ниже. Примечание:
- Запросы на скачивание находятся в асинхронной очереди:
_shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
- Свойства ответа на анализ и обновления синхронизируются в очереди:
_shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
- Всякий раз, когда синтаксический анализ завершается, я выполняю сохранение
newContext
и вызываюupdateProductActiveStatus:
в той же последовательной очереди. Если все запросы выполнены, выполните пакетное обновление статуса. Поскольку запрос выполняется в параллельной очереди, он всегда завершается раньше, чем сохранение (последовательной) очереди, так что это в значительной степени надежный процесс.
Код:
// Load Manager
- (void)resetProductUpdatedStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.propertiesToUpdate = @{ @"hasUpdated" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update hasUpdated: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
[[CoreDataUtil managedObjectContext] performBlockAndWait:^{
[[CoreDataUtil managedObjectContext] refreshAllObjects];
}];
}];
}
- (void)updateProductActiveStatus:(SyncComplete)callback
{
if (self.apiRequestList.count) return;
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.predicate = [NSPredicate predicateWithFormat:@"hasUpdated = NO AND isActive = YES"];
request.propertiesToUpdate = @{ @"isActive" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update isActive: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
NSManagedObjectContext *maincontext = [CoreDataUtil managedObjectContext];
NSLog(@"Refreshed master");
[maincontext performBlockAndWait:^{
[maincontext refreshAllObjects];
NSLog(@"Refreshed main");
// Callback
if (callback) dispatch_async(dispatch_get_main_queue(), ^{ callback(YES, nil); });
}];
}];
}