NSFetchedResultsController / CoreData с проблемой нескольких потоков

В настоящее время я разрабатываю приложение, которое использует CoreData и NSFectchedResultsController. Это приложение содержит только один UITableView, который использует NSFetchedResultsController.

1 / При запуске приложения отключается другой поток. В этом новом потоке вызов WS позволяет получать данные с веб-сервера. После вызова WS я сохраняю данные в своей базе данных CoreData с другим NSManagedObjectContext (лучшая практика Apple: Другой поток => Другой контекст). Я должен удалить все объекты этого объекта перед сохранением новых объектов. Я объединяю этот другой контекст с основным контекстом через mergeChangesFromContextDidSaveNotification.

        // Data Manager (in another thread)
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
        [[NSNotificationCenter defaultCenter]   addObserver:self
                                                selector:@selector(contextDidSave:)
                                                name:NSManagedObjectContextDidSaveNotification
                                                object:context];
        [context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [context setPersistentStoreCoordinator:[self getPersistentStoreCoordinator]];

        ...

        for (NSManagedObject * obj in objects) 
        {
            [context deleteObject:obj];
        }

        ...

        for(NSDictionary *serverObj in serverObjects)
        {
            objAd =  [NSEntityDescription
                      insertNewObjectForEntityForName:@"MyEntity" 
                      inManagedObjectContext:context];
            ...
        }

        [context save:&error];

        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:context];
        [context release];

        ...

        - (void)contextDidSave:(NSNotification *)notification
        {

            SEL selector = @selector(mergeChangesFromContextDidSaveNotification:); 
            [[self getContext] performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
        }

        - (NSManagedObjectContext *) getContext
        {
            return [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
        }

        - (NSPersistentStoreCoordinator *) getPersistentStoreCoordinator
        {
            return [(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator];
        }

2 / Вот мой геттер NSFectchedResultsController:

        // UIView
    - (NSFetchedResultsController*) offersFRC {

        if (offersFRC == nil) 
        {
            NSManagedObjectContext *l_ManagedObjectContext = [[DataManager sharedDataManager] getContext];

            NSFetchRequest *l_FetchRequest = [[NSFetchRequest alloc] init];

            NSEntityDescription *l_Entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:l_ManagedObjectContext];
            [l_FetchRequest setEntity:l_Entity];

            [l_FetchRequest setFetchBatchSize:5];

            NSNumber *sortType = [self.searchCriterions objectForKey:@"sortType"];
            NSSortDescriptor *l_SortDescriptor = [[NSSortDescriptor alloc] initWithKey:[Constants getFieldNameBySortType:sortType] ascending:[Constants isAscendingBySortType:sortType]];
            [l_FetchRequest setSortDescriptors:[NSArray arrayWithObjects:l_SortDescriptor, nil]];
            [l_SortDescriptor release];

            NSFetchedResultsController *l_FetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:l_FetchRequest                                                                                                      managedObjectContext:l_ManagedObjectContext 
                sectionNameKeyPath:nil                                                                                                     
                cacheName:nil];
            [l_FetchRequest release];

            [self setOffersFRC:l_FetchedResultsController];
            [l_FetchedResultsController release],l_FetchedResultsController = nil;

            [self.offersFRC setDelegate:self];
        }

        return offersFRC;
    }

3 / У меня при запуске приложения возникает следующая ошибка:

        2012-02-29 11:56:09.119 Nanopost[1996:207] *** Terminating app due to uncaught exception      'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x5c3c760 <x-coredata://E176B0A1-275B-4332-9231-49FD88238C2B/Ads/p231>''
    *** Call stack at first throw:
    (
    0   CoreFoundation                      0x02bfe919 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x02e595de objc_exception_throw + 47
    2   CoreData                            0x028b833f _PFFaultHandlerLookupRow + 1407
    3   CoreData                            0x028b5ee3 _PF_FulfillDeferredFault + 499
    4   CoreData                            0x028b9f3f _sharedIMPL_pvfk_core + 95
    5   CoreData                            0x0292a010 _PF_Handler_Public_GetProperty + 160
    6   Foundation                          0x02442c4f -[NSSortDescriptor compareObject:toObject:] + 128
    7   CoreData                            0x0297db5e +[NSFetchedResultsController(PrivateMethods) _insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 286
    8   CoreData                            0x0297e1b2 -[NSFetchedResultsController(PrivateMethods) _postprocessInsertedObjects:] + 402
    9   CoreData                            0x029841bc -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 1804
    10  Foundation                          0x02380c1d _nsnote_callback + 145
    11  CoreFoundation                      0x02bd6cf9 __CFXNotificationPost_old + 745
    12  CoreFoundation                      0x02b5611a _CFXNotificationPostNotification + 186
    13  Foundation                          0x023767c2 -[NSNotificationCenter postNotificationName:object:userInfo:] + 134
    14  CoreData                            0x028c0519 -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 89
    15  CoreData                            0x028f802b -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] + 1579
    16  Foundation                          0x02395e9a __NSThreadPerformPerform + 251
    17  CoreFoundation                      0x02bdfd7f __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
    18  CoreFoundation                      0x02b3e2cb __CFRunLoopDoSources0 + 571
    19  CoreFoundation                      0x02b3d7c6 __CFRunLoopRun + 470
    20  CoreFoundation                      0x02b3d280 CFRunLoopRunSpecific + 208
    21  CoreFoundation                      0x02b3d1a1 CFRunLoopRunInMode + 97
    22  GraphicsServices                    0x031e62c8 GSEventRunModal + 217
    23  GraphicsServices                    0x031e638d GSEventRun + 115
    24  UIKit                               0x0063cb58 UIApplicationMain + 1160
    25  Nanopost                            0x0000230a main + 170
    26  Nanopost                            0x00002255 start + 53
    )
    terminate called after throwing an instance of '_NSCoreDataException'

Важные примечания:

  • Вылетает только на iOS4
  • controllerWillChangeContent - это последняя функция, вызываемая в моем коде перед сбоем приложения. controllerDidChangeContent / didChangeObject / didChangeSection не вызываются.
  • Когда я комментирую [l_FetchRequest setFetchBatchSize: 5] => Больше никаких сбоев
  • Когда я добавляю [context save: & error] после удаления объектов и перед вставкой новых объектов => Больше никаких сбоев
  • Когда я использую [l_FetchRequest setFetchBatchSize: 24] => Сбои
  • Когда я использую [l_FetchRequest setFetchBatchSize: 25] => Больше никаких сбоев

Я потратил много времени, пытаясь разобраться в этой проблеме, поэтому заранее большое спасибо за ваши ответы!

Томас

Редактировать 1 (@Jody): Привет, Джоди, большое спасибо за ваши ответы!

Вот код, используемый для обработки contextDidSave:

    - (void)contextDidSave:(NSNotification *)notification
    {
        SEL selector = @selector(mergeChangesFromContextDidSaveNotification:); 
        [[self getContext] performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
    }

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

В этом приложении я использую 2 контекста:

  • № 1: Создается по умолчанию в AppDelegate при создании XCodeProject. Этот контекст используется моим FRC и позволяет отображать строки UITableView.

  • №2: Созданный в моем «DataManager» (первый блок кода в моем сообщении) синглтон, который позволяет обновлять мою БД (вызов WS, удаление, повторная вставка, сохранение).

При сохранении контекста № 2 вызывается contextDidSave, чтобы объединить этот контекст с основным контекстом (контекст № 1). После этого вызывается метод моего делегата FRC «controllerWillChangeContent». Я не думаю, что это поможет показать код, содержащийся в этом методе, потому что даже если я просто поставлю NSLog, он выйдет из строя после этого метода (я поместил много NSLog, и NSLog, содержащийся в controllerWillChangeContent, будет последним, который отображается перед авария).

Я разместил сообщение на форуме Apple Dev Forum, и есть интересный ответ: https://devforums.apple.com/thread/152172?tstart=0

Редактировать 2 (@Jody): Привет, Джоди!

Как вы можете видеть в следующем методе, мой FRC не использует MOC из другого потока:

- (NSManagedObjectContext *) getContext
    {
        return [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    }

Этот метод моего "DataManager" возвращает MOC AppDelegate (= MOC основного потока)


person Thomas Lupo    schedule 24.05.2012    source источник


Ответы (1)


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

Какой у вас код для обработки contextDidSave :? Кроме того, что ваш делегат FRC делает с уведомлениями об обновлении данных?

ИЗМЕНИТЬ

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

// Data Manager (in another thread)
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];

А затем, используя MOC "по умолчанию", чтобы вытащить эти данные в ваше приложение через уведомление, но похоже, что полученный вами контроллер результатов также пытается использовать MOC из другого потока ...

NSManagedObjectContext *l_ManagedObjectContext = [[DataManager sharedDataManager] getContext];

Вероятно, он также должен использовать основной MOC (от делегата вашего приложения).

person Jody Hagins    schedule 24.05.2012
comment
Не следует помещать код в комментарий, потому что он не имеет форматирования. Кроме того, лучше всего отредактировать вопрос, чтобы получить лучший вопрос. Что касается кода, прежде чем я даже коснусь обработки DidSave, вы сначала должны рассказать мне больше о контексте, который вы используете ... опять же, откуда он взялся? Как это было создано? Каково его отношение к контекстам MR? - person Jody Hagins; 25.05.2012
comment
Ой. Те же вопросы, но без учета части MR (отвечал в дороге (в Hardees) и смешал его с другим вопросом. Извините за путаницу. - person Jody Hagins; 25.05.2012