Основные данные: NSManagedObjectContext, NSFetchResultsController и UIManagedDocument.

У меня возникают проблемы с использованием фоновых потоков для обновления данных в UIManagedDocument/Core Data. В частности, я использую NSFetchResultsController для обновления аннотаций карты на основе геокодированных данных из фонового потока (после объединения с моим основным MOC), но карта никогда не обновляется из-за того, как UIManagedDocument передает данные в свои хранилища и/или в свои несколько MOC (родительский и дочерний). Если я закрою приложение и снова открою, аннотации будут заполнены, поэтому в какой-то момент происходит фиксация в постоянном хранилище, но мне неясно, как принудительно выполнить такую ​​фиксацию, которая, таким образом, обновит файл NSFetchResultsController. Вот код:

Фоновый поток, обновляющий MOC:

- (void) populateGPSCoordsInClubsInContext: (NSManagedObjectContext *) mainCtx
{            
    dispatch_queue_t MapFetchQ = dispatch_queue_create("Google Map Data Fetcher", NULL);
    dispatch_async(MapFetchQ, ^{

        NSManagedObjectContext * ctxThread = [[NSManagedObjectContext alloc] init];
        [ctxThread setPersistentStoreCoordinator:mainCtx.persistentStoreCoordinator];


        NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"];
        request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@", self.name];
        NSError *error = nil;

        NSArray * clubs = [ctxThread executeFetchRequest:request error:&error];

        NSLog(@"[%@] Fetching map data. Club count is %d", self.name, [clubs count]);  

        int delayCounter = 0;

        for(Club * club in clubs)
        {
            if(![club.hasCoord boolValue] && club != nil)
            {
                delayCounter++; // to deal with google maps api's DoS protection            

                [club setLongitudeAndLattitudeFromGoogle];
                NSError * error;

                if(![ctx save:&error])
                 NSLog(@"[%@] Problem saving region to database.", self.name);

            }

            if(delayCounter == 8)
            {            
                [NSThread sleepForTimeInterval:(NSTimeInterval)2.0];
                delayCounter = 0;
            }
        }
    });
    dispatch_release(MapFetchQ);
}

Когда эти сохранения вызываются, я получаю уведомление в основном потоке (в моем делегате приложения) следующим образом:

- (void) contextDidSave: (NSNotification *) notification
{
    NSManagedObjectContext * ctx = [self.clubsDB managedObjectContext];
    [ctx mergeChangesFromContextDidSaveNotification:notification];

    NSArray * updates = [[notification.object updatedObjects] allObjects];

    for(Club * club in updates) // This never fires because updates never has objects
    {
        NSLog(@"*********** %@", club.name);
    }

    NSLog(@"[%@] %@", [self class], NSStringFromSelector(_cmd));

}

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

-(void) setupFRC
{

    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"];
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
    request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@ AND hasCoord=%@",[self.clubsDB regionTitleAsString], [NSNumber numberWithBool:YES]]; // Follow the relationshop and only display clubs from THIS region.

    //request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@",[self.clubsDB regionTitleAsString]];

    self.debug = YES;

    self.fetchedResultsController = 
    [[NSFetchedResultsController alloc] initWithFetchRequest:request 
                                        managedObjectContext:self.clubsDB.managedObjectContext
                                          sectionNameKeyPath:nil
                                                   cacheName:nil];
}

Любые идеи относительно того, как я могу обновить соответствующий MOC, чтобы получить желаемый контроллер результатов?


person Andrew    schedule 23.03.2012    source источник
comment
Кто-нибудь вообще? Совсем запутался.. :/   -  person Andrew    schedule 27.03.2012


Ответы (3)


У меня проблема с тем, как UIManagedDocument отправляет свои коммиты. Единственное решение, которое я могу придумать, — это прекратить использование UIManagedDocument и просто использовать контекст из PersistentStore, как это предусмотрено в шаблоне Master-Detail по умолчанию.

edit: после дальнейших исследований кажется, что UIManagedDocument не может зафиксировать изменения, поэтому, вероятно, лучше передать контекст, созданный из постоянного хранилища. Кажется, это не совпадение, что Apple еще не предоставила какой-либо пригодный для использования пример кода для UIManagedDocument. Я бы придерживался шаблона по умолчанию.

Вот ссылка на человека, столкнувшегося с похожей проблемой, и его «решение» — иногда лучшее решение — знать, что его нет :P

Управляемый объект Core Data не видит связанных объектов до перезапуска Симулятора

person Jack L.    schedule 02.04.2012

Я действительно смог решить проблему. Хитрость заключалась в том, чтобы обеспечить предварительное заполнение контекста объектами, которые я редактировал до установки FRC. Откровенно говоря, это было крайне эзотерично, и тот факт, что UIManagedDocument не работает так, как ожидалось (или даже так, как это объясняется в документации), сбивает с толку.

person Andrew    schedule 03.04.2012

Хорошо, я собираюсь отредактировать ваш код, чтобы дать вам представление о том, как он должен выглядеть. Я предполагаю, что вы передаете MOC из UIManagedDocument в populateGPSCoordsInClubsContext. Обратите внимание, что разница в том, что вы уже делаете, очень мала, но, как мы знаем, одна строка кода может иметь все значение...

// NOTE: Make it clear you expect to work on a document...
- (void) populateGPSCoordsInClubsInContext: (UIManagedDocument *) document
{            
    dispatch_queue_t MapFetchQ = dispatch_queue_create("Google Map Data Fetcher", NULL);
    dispatch_async(MapFetchQ, ^{

        NSManagedObjectContext * ctxThread = [[NSManagedObjectContext alloc] init];
        // NOTE: Make changes up into the context of the document
        ctxThread.parentContext = document.managedObjectContext;    

        NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"];
        request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@", self.name];
        NSError *error = nil;

        NSArray * clubs = [ctxThread executeFetchRequest:request error:&error];

        NSLog(@"[%@] Fetching map data. Club count is %d", self.name, [clubs count]);  

        int delayCounter = 0;

        for(Club * club in clubs)
        {
            if(![club.hasCoord boolValue] && club != nil)
            {
                delayCounter++; // to deal with google maps api's DoS protection            

                [club setLongitudeAndLattitudeFromGoogle];
                NSError * error;

                // NOTE: This notifies the parent context of the changes.
                if(![ctx save:&error])
                 NSLog(@"[%@] Problem saving region to database.", self.name);
                // NOTE: However, since a UIManagedDocument is an "auto-save"
                // document, we need to tell it that is is dirty...
                [document updateChangeCount:UIDocumentChangeDone];
            }

            if(delayCounter == 8)
            {            
                [NSThread sleepForTimeInterval:(NSTimeInterval)2.0];
                delayCounter = 0;
            }
        }
    });
    dispatch_release(MapFetchQ);
}

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

Если вы хотите сделать это по-другому, вы можете сделать это...

        ctxThread.parentContext = document.parentContext;    

Тогда вы не стали бы вызывать updateChangeCount для документа. Эти изменения попадут в родительский контекст и в файл. Однако при этом вам также не нужно обрабатывать уведомления, потому что будущие выборки автоматически их увидят. Конечно, если вы хотите обновлять информацию об изменениях, вы все равно можете обрабатывать уведомления, но это все. Чтобы увидеть их на выборке, вам больше ничего не нужно делать.

Вопреки распространенному мнению, UIManagedDocument действительно довольно эффективен и прост (если вы знаете правила).

person Jody Hagins    schedule 17.04.2012