NSFetchedResultsController返回ghost记录

时间:2013-11-20 08:23:18

标签: ios objective-c core-data

几个月来我一直困扰着一个问题,我终于需要一劳永逸地解决这个问题。

我有一个由NSFetchedResultsController提供的表视图。这附在核心数据上。我使用单独的类来填充来自Web服务的核心数据。

以下是该方案: 您加载表,NSFetchedResultsController从NSManagedObjectContext返回10行。

您编辑记录1。

然后,您向要进行更新的数据库类发出信号。这些类上载更改的记录,然后请求自上次更新以来更改的Web服务中的任何记录。此结果集包括更新的记录1.我的类从NSManagedObjectContext中删除记录1,然后插入从Web服务下载的新记录1并提交更改。

数据库类现已完成。

tableView现在需要更新。 NSFetchedResultsController执行新的提取。 NSManagedObjectContext返回10条记录,包括从Web服务下载的新记录1,记住已上载的旧记录1已被删除。

我们现在打开记录2或添加新记录,只需访问表中的记录。

现在尝试向NSFetchedResultsController询问记录列表(比如[self.tableView reloadData]现在返回11条记录,NSManagedObjectContext中存在的10条记录加上我们删除的旧记录1.我已经完成了代码处理这一点并在此处放入各种NSLog,检查NSManagedObjectContext并确认:

NSManagedObjectContext包含10条记录。 NSFetchedResultsController包含11条记录。

我在更新NSManagedObjectContext以尝试刷新FetchedResultsController后返回数据库类后立即使用self.NSFetchedResultsController = nil,并且它执行了一次新的获取(毕竟,它确实在此点之后正确返回10条记录) ,但在您尝试更改或访问任何记录后,它仍会返回11条记录。

有谁知道这个鬼记录的来源?它确实似乎来自NSFetchedResultsController,因为NSManagedObjectContext从来没有包含11条记录,也没有fetch命令返回11条记录。

如果你试图打开这个鬼记录,你会得到一个断言失败,因为系统试图完成一个显然不存在的错误。

对于任何感兴趣的人,这是从网络服务加载数据的方式: UITableViewController实例化一个负责数据存储的类,称为datastoreSync。然后我们创建一个后台线程,在这个后台线程中,我们从原始线程创建NSManagedObjectContext的新实例,然后将其分配给datatoreClass并设置委托以及来自NSManagedObjectContext的通知,该通知与主线程的NSManagedObjectContext同步更新后等。

完成此操作后,datastoreSync类会在进程结束时引发委托,以通知我们更新已完成。为了解决上述问题,我尝试在此时使NSFetchedResultsController无效并触发tableview重新加载,但它还没有解决问题。

这里要求的是NSFetchedResultsController的代码

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ShiftLog" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];

// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"logNumber" ascending:NO];
NSSortDescriptor *sortDescriptorProgress = [[NSSortDescriptor alloc] initWithKey:@"pendingUpload" ascending:NO];
NSSortDescriptor *sortDescriptorComplete = [[NSSortDescriptor alloc] initWithKey:@"complete" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptorProgress, sortDescriptorComplete, sortDescriptor];


[fetchRequest setSortDescriptors:sortDescriptors];

// Load an externally derived asset if needed.

NSPredicate *predicate = [self getFilterPredicate];
if(predicate !=nil){
    [fetchRequest setPredicate:predicate];
}

// Remember the request.
_request = fetchRequest;

// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;


if (self.fetchedResultsController.fetchedObjects == nil){
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        DLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}else{
    DLog(@"Caught a populated fetchresultscontroller");
}
return _fetchedResultsController;

}

根据要求,您可以获得

的错误副本

由于未捕获的异常终止应用程序' NSObjectInaccessibleException',原因:' CoreData无法解决' 0x9b5a7c0''' * 第一次抛出调用堆栈: (     0 CoreFoundation 0x02ad25e4 exceptionPreprocess + 180     1 libobjc.A.dylib 0x026698b6 objc_exception_throw + 44     2 CoreData 0x0233f33b _PFFaultHandlerLookupRow + 2715     3 CoreData 0x0233e897 - [NSFaultHandler fulfillFault:withContext:forIndex:] + 39     4 CoreData 0x0233e473 _PF_FulfillDeferredFault + 259     5 CoreData 0x0233e2c6 _sharedIMPL_pvfk_core + 70     6 CoreData 0x0234a4d0 _pvfk_7 + 32     7 [projectname] 0x00040170 - [DetailedViewController configureView] + 336     8 [projectname] 0x0003dfcb - [DetailedViewController setShiftLogObject:] + 331     9 [projectname] 0x000063ac - [MasterViewController alertView:clickedButtonAtIndex:] + 828     10 UIKit 0x01349ef3 - [UIAlertView(Private)modalItem:tappedButtonAtIndex:] + 67     11 UIKit 0x01417785 - [_ UIModalItemsCoordinator _notifyDelegateModalItem:tappedButtonAtIndex:] + 180     12 UIKit 0x00f7305b - [_ UIModalItemAlertContentView tableView:didSelectRowAtIndexPath:] + 380     13 UIKit 0x00f4a7b1 - [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1513     14 UIKit 0x00f4a924 - [UITableView _userSelectRowAtPendingSelectionIndexPath:] + 279     15 UIKit 0x00f4e908 __38- [UITableView touchesEnded:withEvent:] _ block_invoke + 43     16 UIKit 0x00e85183 ___afterCACommitHandler_block_invoke + 15     17 UIKit 0x00e8512e _applyBlockToCFArrayCopiedToStack + 403     18 UIKit 0x00e84f5a _afterCACommitHandler + 532     19 CoreFoundation 0x02a9a4ce __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 30     20 CoreFoundation 0x02a9a41f __CFRunLoopDoObservers + 399     21 CoreFoundation 0x02a78344 __CFRunLoopRun + 1076     22 CoreFoundation 0x02a77ac3 CFRunLoopRunSpecific + 467     23 CoreFoundation 0x02a778db CFRunLoopRunInMode + 123     24 GraphicsServices 0x034e09e2 GSEventRunModal + 192     25 GraphicsServices 0x034e0809 GSEventRun + 104     26 UIKit 0x00e68d3b UIApplicationMain + 1225     27 [projectname] 0x0000258d main + 141     28 libdyld.dylib 0x0397970d start + 1 ) libc ++ abi.dylib:以_NSCoreDataException类型的未捕获异常终止

1 个答案:

答案 0 :(得分:0)

嗯这是一个奇怪的,但我已经做到了我现在需要的东西。当我开始后台任务更新时,我将它传递给它自己的NSManagedObjectContext。我将主线程设置为此NSManagedObjectContext中的save事件的订阅者,以便我可以在后台MOC执行保存时更新主线程MOC。

- (void)mergeChanges:(NSNotification *)notification
{
DLog(@"Merge changes has begun");

// Merge changes into the main context on the main thread
NSManagedObjectContext *incommingContext = [notification object];

if (incommingContext != self.managedObjectContext){
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    });
}
//[self compareMOCs];
}

当您操作NSManagedObjectContext rollback时,NSManagedObjectContext不会回滚(因为它已保存),但NSFetchedResultsController会执行并显示两个已删除的记录。

要解决这个问题,我必须在保存合并后将主线程NSManagedObjectContext保存在主线程中,如下所示:

- (void)mergeChanges:(NSNotification *)notification
{
DLog(@"Merge changes has begun");

// Merge changes into the main context on the main thread
NSManagedObjectContext *incommingContext = [notification object];

if (incommingContext != self.managedObjectContext){
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // no idea why I have to do this but if I do not do this any
        // attempt to rollback the MOC will cause the NSFetchedResultsController to pull out
        // ghost records.
        [self.managedObjectContext save:nil];

    });
}
//[self compareMOCs];
}

根据Apple的说法,这不应该被要求,因为通知仅在后台MOC上的保存事件之后被触发。我无法解释为什么会发生这种情况,我只能告诉你它正在发生,这个额外的保存已经解决了。