Подтвердить что ты не робот

NSFetchedResultsController не показывает обновления из другого контекста

У меня есть NSFetchedResultsController, а несколько операций обновляют управляемые объекты в отдельных потоках через NSOperationQueue.

FRC (с его предикатом) выглядит так:

- (NSFetchedResultsController*)fetchedResultsController
{
    if(fetchedResultsController) return fetchedResultsController;

    NSManagedObjectContext* mainContext = [self managedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Check" inManagedObjectContext:mainContext]];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"isSync == %@", [NSNumber numberWithBool:NO]]];
    [fetchRequest setFetchBatchSize:10];

    fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
    fetchedResultsController.delegate = self;

    [fetchRequest release], fetchRequest = nil;

    return fetchedResultsController;
}

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

Внутри резьбовой операции я изменяю свойство isSync от NO до YES. Чтобы узнать, что требуется для обновления объекта Check, главный контекст переходит к потоковому a NSManagedObjectID. Операция с резьбой возвращает управляемый объект следующим образом:

-(void)main
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init];
    [exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];

    //...

    Check* check = (Check*)[exportContext existingObjectWithID:objID error:&error];
    check.isSync = [NSNumber numberWithBool:YES];

    //...

    [exportContext save:&error];

    [pool release], pool = nil;
}

Когда операция потока вызывает save вызывается уведомление mergeChangesFromContextDidSaveNotification, а основной контекст объединяет изменения.

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == [self managedObjectContext]) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}

Запись журнала notification позволяет проверить правильность выполненных изменений.

Моя проблема

Делегированные методы NSFetchedResultsControllerDelegate не вызываются.

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

Я нашел несколько тем на SO с той же проблемой. Я пробовал все обходные пути, но я не могу найти ценное решение:

Спасибо заранее.

Edit

Приведенный выше код работал в предыдущей модели. Затем я создал новую модель, копирующую (и вставляя) сущности из предыдущей, и теперь она больше не работает.

Предложения?

Изменить 2

Это предикат, который я использую в NSFetchedResultsController getter. Это моя вина, но когда я написал сообщение, я не копировал его.

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"insertionDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

// previous code here
[fetchRequest setSortDescriptors:sortDescriptors];

Теперь о Jody последнем комментарии

В основном() вашего NSOperation вы загружаете новые объекты, а в там похоже, что вы устанавливаете isSync в YES для каждого нового объекта. Предикат, который вы используете для fetchedResultsController, выглядит только для объектов, которые имеют isSync == NO.

Я ожидаю, что, когда свойство isSync установлено в YES, NSFetchedResultsController отмечает, что он изменяет и удаляет строки, которые не соответствуют предикату. Я не прав?

Помните, что при слиянии изменений от фона к основному потоку я могу видеть, что несколько объектов обновили их свойство isSync.

4b9b3361

Ответ 1

У вас есть основная идея, поэтому, вероятно, в вашем коде есть ошибка...

Дважды проверьте, что вы правильно регистрируетесь, чтобы получать уведомление из фонового MOC.

Зарегистрируйтесь, чтобы получать все уведомления от всех объектов. В этом методе зарегистрируйте событие и все его данные. Когда объект является MOC, выгрузите все его свойства (особенно списки зарегистрированных, вставленных, обновленных и удаленных объектов).

Поместите оператор журнала непосредственно перед и после вызова сохранения и в обработчике уведомлений для объединения уведомления.

Кроме того, вы опустили много кода, чтобы было сложно узнать, что вы на самом деле делаете, но пример кода, который вы включили, выглядит как жесткая настройка isSync для YES для всех загружаемых объектов, но ваш запрос на выборку требует только тех с isSync установлено значение NO. Ни один из этих новых объектов не пройдет этот предикат.

Наконец, дважды проверьте свое определение модели и убедитесь, что вы используете правильный тип номера. Это может быть большим источником проблем.

ИЗМЕНИТЬ

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

Чтобы следить за комментариями Alexsander... Я упоминал об этом в начале моего сообщения, но вы, конечно же, не хотите слушать уведомления от MOC, если он не известен как один из ваших (если, конечно, вы просто регистрируетесь для целей отладки). Вы должны знать о MOC, который используете.

Кроме того, я бы предложил использовать родительские/дочерние MOC для этого типа обработки, но то, что вы делаете, должно работать, если выполнено правильно.

Родительский (закрытый concurrency тип) Основной (основной тип concurrency)

Затем, с помощью фоновых MOC, просто установите для них основной moc в качестве родителя. Когда они сохраняют, их объекты вводятся непосредственно в основной MOC. Затем основной MOC выдает сбережения в последующие моменты времени, чтобы поместить их на диск.

Или вы можете родительский фоном MOC для "родителя", а затем "основной" MOC может просто переиздать выборку, чтобы получить данные от родителя.

Ответ 2

У меня была одна и та же проблема, которую я решил с помощью родительских/дочерних контекстов. Вот проблема, которую я имел.

Я обновлял свой графический объект Core Data в фоновом потоке, у которого был свой собственный managedObjectContext (который является обязательным), а мой fetchedResultsController не смог получить изменения, внесенные в базу данных.

После того, как я решил, я сделал несколько замечаний:

managedObjectContext не являются потокобезопасными, а это значит, что managedObjectContext нельзя использовать совместно с другими потоками. Если поток должен использовать managedObjectContext, он инициализирует свой собственный managedObjectContext.

Для инициализации a managedObjectContext существует два способа:

  • alloc/init затем установите его свойство persistentStoreCoordinator

  • alloc/init затем установите свойство parentContext вместо свойства persistentStoreCoordinator

note: нельзя установить как persistentStoreCoordinator, так и parentContext свойство managedObjectContext.

Использование контекста parent/child необходимо, когда контекст выполняется в фоновом потоке, который "связан с контроллерами и объектами пользовательского интерфейса, которые должны использоваться только в основном потоке" (документация по основным данным).

Вот требования, необходимые для контекста parent/child:

  • родительский контекст живет в основном потоке

  • дочерний контекст живет в фоновом потоке

  • оба контекста должны быть инициализированы с помощью метода initWithConcurrencyType:NSMainQueueConcurrencyType.

  • когда пакет изменений был выполнен в дочернем контексте, оба контекста должны выполнить операцию сохранения. Эти операции сохранения должны быть вложены в методы executeBlock, то есть:

    childContext performBlock:^{
        [childContext save:nil];
        [self.parentContext performBlock:^{
            [self.parentContext save:nil];                
        }];
    }];
    

EDIT: приведенный выше код на самом деле плохой идеей по двум причинам:

1) Он работает без сохранения родительского контекста.

2) Основной поток блокируется, если на нем выполняется родительский контекст.

Надеюсь, это помогло!

EDIT: Вот поток stackoverflow, который очень помог мне: Родственному объекту Core Data ManagedObjectContext необходимо разделить тип concurrency с детским контекстом?