NSSortDescriptor NSFetchRequest не работает после сохранения контекста

Я выполняю операции в очереди отправки GCD в NSManagedObjectContext, определенном следующим образом:

- (NSManagedObjectContext *)backgroundContext
{
    if (backgroundContext == nil) {
        self.backgroundContext = [NSManagedObjectContext MR_contextThatNotifiesDefaultContextOnMainThread];
    }
    return backgroundContext;
}

MR_contextThatNotifiesDefaultContextOnMainThread — это метод из MagicalRecord:

NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[NSManagedObjectContext MR_defaultContext]];
return context;

После извлечения моих объектов и указания им правильной позиции в очереди я регистрирую их, и порядок правильный. Однако второй журнал выглядит совершенно случайным, дескриптор сортировки явно не работает.

Я сузил проблему до [self.backgroundContext save:&error]. После сохранения фонового контекста дескрипторы сортировки не работают.

dispatch_group_async(backgroundGroup, backgroundQueue, ^{
    // ...

    for (FooObject *obj in fetchedObjects) {
        // ...
        obj.queuePosition = [NSNumber numberWithInteger:newQueuePosition++];
    }

    NSFetchRequest *f = [NSFetchRequest fetchRequestWithEntityName:[FooObject entityName]];
    f.predicate = [NSPredicate predicateWithFormat:@"queuePosition > 0"];
    f.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"queuePosition" ascending:YES]];
    NSArray *queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
    for (FooObject *obj in queuedObjects) {
        DLog(@"%@ %@", obj.queuePosition, obj.title);
    }

    if ([self.backgroundContext hasChanges]) {
        DLog(@"Changes");
        NSError *error = nil;
        if ([self.backgroundContext save:&error] == NO) {
            DLog(@"Error: %@", error);
        }
    }

    queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
    for (FooObject *obj in queuedObjects) {
        DLog(@"%@ %@", obj.queuePosition, obj.title);
    }

});

Я понятия не имею, почему дескриптор сортировки не работает, кто-нибудь из экспертов Core Data хочет помочь?

Обновление:

На iOS 4 проблема не возникает. Думаю, причина где-то в разнице между режимами изоляции потоков и приватной очереди. MagicalRecord автоматически использует новый шаблон параллелизма, который ведет себя иначе.

Обновление 2:

Проблема решилась добавлением сохранения фонового контекста:

if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
    DLog(@"Changes");
    NSError *error = nil;
    if ([[NSManagedObjectContext MR_contextForCurrentThread] save:&error] == NO) {
        DLog(@"Error: %@", error);
    } else {
        NSManagedObjectContext *parent = [NSManagedObjectContext MR_contextForCurrentThread].parentContext;
        [parent performBlockAndWait:^{
            NSError *error = nil;
            if ([parent save:&error] == NO) {
                DLog(@"Error saving parent context: %@", error);
            }
        }];
    }
}

Обновление 3:

MagicalRecord предлагает метод рекурсивного сохранения контекста, теперь мой код выглядит так:

if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
    DLog(@"Changes");
    [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveWithErrorHandler:^(NSError *error) {
        DLog(@"Error saving context: %@", error);
    }];
}

Позор мне, что я не использовал его в первую очередь...

Однако я не знаю, почему это помогает, и хотел бы получить объяснение.


person tim    schedule 12.04.2012    source источник


Ответы (3)


Попробую прокомментировать, раз уж написал MagicalRecord.

Итак, в iOS5 MagicalRecord настроен на использование нового метода Private Queue для нескольких контекстов управляемых объектов. Это означает, что сохранение в дочернем контексте отправляет сохранения только родительскому. Только когда родитель, у которого больше нет родителей, сохраняет сохранение, сохраняется в своем хранилище. Это, вероятно, то, что происходило в вашей версии MagicalRecord.

MagicalRecord пытался справиться с этим для вас в более поздних версиях. То есть он будет пытаться выбирать между режимом частной очереди и режимом изоляции потока. Как вы поняли, это не слишком хорошо работает. Единственный по-настоящему совместимый способ написания кода (без сложных правил препроцессора и т. д.) для iOS4 и iOS5 — использовать классический режим изоляции потоков. MagicalRecord из тега 1.8.3 поддерживает это и должен работать для обоих. Начиная с версии 2.0, это будут только частные очереди.

И, если вы посмотрите на метод MR_save, вы увидите, что он также выполняет проверку hasChanges для вас (которая также может быть ненужной, поскольку внутренние компоненты Core Data могут справиться и с этим). Во всяком случае, меньше кода вам придется писать и поддерживать...

person casademora    schedule 13.04.2012
comment
Спасибо за ваш вклад, я еще немного поэкспериментировал только с изоляцией потоков, но я бы предпочел использовать частные очереди в iOS 5. Кажется, во время тестирования я нашел ошибку. Сохранение родительского контекста вызывается в потоке дочернего контекста, что приводит к проблемам при рекурсивном сохранении из контекста GCD. Я отправил запрос на вытягивание: github.com/magicalpanda/MagicalRecord/pull/159 - person tim; 13.04.2012

Фактическая основная причина, по которой ваша первоначальная настройка не работала, — это ошибка Apple при извлечении из дочернего контекста с дескрипторами сортировки, когда родительский контекст еще не сохранен для хранения:

дескриптор NSSort неэффективен при получении результата из NSManagedContext

Если есть какой-то способ избежать вложенных контекстов, избегайте их, так как они все еще крайне глючны, и вы, вероятно, будете разочарованы предполагаемым приростом производительности, ср. также: http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains

person Andreas Zollmann    schedule 11.12.2012

Поскольку CoreData не является платформой безопасных потоков, и для каждого потока (очереди операций) основные данные используют разные контексты. Пожалуйста, обратитесь к следующему отличному письму

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

person Black Coder    schedule 12.04.2012
comment
backgroundContext инициализируется из блока GCD, и ни один ManagedObject или ManagedObjectContext не пересекает границы потока, поэтому я не думаю, что это моя проблема. - person tim; 12.04.2012