ios - 具有多线程问题的 NSFetchedResultsController/CoreData

标签 ios uitableview core-data nsfetchedresultscontroller nsmanagedobjectcontext

我目前正在开发一个使用 CoreData 和 NSFectchedResultsController 的应用程序。此应用程序仅包含一个使用 NSFetchedResultsController 的 UITableView。

1/ 当应用程序启动时,另一个线程被分离。在这个新线程中,WS 调用允许从 Web 服务器检索数据。在 WS 调用之后,我使用另一个 NSManagedObjectContext(Apple 的最佳实践:另一个线程 => 另一个上下文)将数据存储在我的 CoreData 数据库中。在保存新对象之前,我必须删除该实体的所有对象。我通过 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 的 getter:

        // 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 个上下文:

  • N° 1:创建 XCodeProject 时在 AppDelegate 中默认创建。此上下文由我的 FRC 使用并允许显示 UITableView 的行。

  • N° 2:在我的“DataManager”(我帖子中的第一个代码块)中创建,一个允许刷新我的数据库(WS 调用、删除、重新插入、保存)的单例。

保存 2 号上下文时,将调用 contextDidSave 将此上下文与主上下文(1 号上下文)合并。之后,我的 FRC 委托(delegate)的方法“controllerWillChangeContent”被调用。我认为显示此方法中包含的代码不会有帮助,因为即使我只放置一个 NSLog,它也会在此方法之后崩溃(我放置了很多 NSLog,而 controllerWillChangeContent 中包含的 NSLog 是最后一个显示在之前的代码崩溃)。

我已经在 Apple Dev Forum 上发帖并且有一个有趣的答案:https://devforums.apple.com/thread/152172?tstart=0

编辑 2 (@Jody): 嗨,乔迪!

正如您在下面的方法中看到的,我的 FRC 没有使用来自其他线程的 MOC:

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

我的“DataManager”的这个方法返回 AppDelegate 的 MOC(= 主线程 MOC)

最佳答案

您正在从一个上下文中的数据库中删除,因此您必须适本地刷新您的其他上下文,特别是,摆脱对已删除数据的对象引用。

处理 contextDidSave: 的代码是什么?另外,您的 FRC 代表如何处理数据更新通知?

编辑

看起来您正在混淆托管对象上下文。您正在数据管理器中使用一个来加载和修改数据库...

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

然后使用“默认”MOC 通过通知将该数据拉入您的应用程序,但看起来您的获取结果 Controller 也在尝试使用来自其他线程的 MOC...

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

它可能也应该使用主 MOC(来自您的应用委托(delegate))。

关于ios - 具有多线程问题的 NSFetchedResultsController/CoreData,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10737781/

相关文章:

iOS UI 自定义优化

iphone - 由于未捕获的异常而终止应用程序,同时加载 View

xcode - 我应该使用 CoreData 谓词来填充我的 UITableview 吗?

ios - RESTKit:具有一对多关系的 POST

ios - 无法在xcode 4中添加数据模型

ios - 如何读取动画后的keyPath值?

iphone - 如何在 iOS 中卸载我们的应用程序时收到通知

ios - 向tableview添加渐变层

UITableView didSelectRowAtIndexPath : not being called on first tap

ios - UISplitViewController : Can DetailViewController load data from MasterVIewController without segueing back?