【问题标题】:NSFetchedResultsController returning ghost recordsNSFetchedResultsController 返回幽灵记录
【发布时间】:2013-12-04 03:18:10
【问题描述】:

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

我有一个由 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 条记录,其中 10 条存在于 NSManagedObjectContext 加上我们删除的旧记录 1。我已经逐步完成了代码并在此时放入各种 NSLog,检查 NSManagedObjectContext 并确认:

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

我在数据库类从更新 NSManagedObjectContext 返回后立即使用self.NSFetchedResultsController = nil 以尝试刷新 FetchedResultsController,并且它进行了新的提取(毕竟,在此之后它确实返回了 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 异常预处理 + 180 1 libobjc.A.dylib 0x026698b6 objc_exception_throw + 44 2 核心数据 0x0233f33b _PFFaultHandlerLookupRow + 2715 3 核心数据 0x0233e897 -[NSFaultHandler 完成故障:withContext:forIndex:] + 39 4 核心数据 0x0233e473 _PF_FulfillDeferredFault + 259 5 核心数据 0x0233e2c6 _sharedIMPL_pvfk_core + 70 6 核心数据 0x0234a4d0 _pvfk_7 + 32 7 [项目名称] 0x00040170 -[详细视图控制器配置视图] + 336 8 [项目名称] 0x0003dfcb -[DetailedViewController setShiftLogObject:] + 331 9 [项目名称] 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 核心基础 0x02a9a4ce __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 30 20 核心基础 0x02a9a41f __CFRunLoopDoObservers + 399 21 核心基础 0x02a78344 __CFRunLoopRun + 1076 22 核心基础 0x02a77ac3 CFRunLoopRunSpecific + 467 23 核心基础 0x02a778db CFRunLoopRunInMode + 123 24 图形服务 0x034e09e2 GSEventRunModal + 192 25 图形服务 0x034e0809 GSEventRun + 104 26 UIKit 0x00e68d3b UIApplicationMain + 1225 27 [项目名称] 0x0000258d 主要 + 141 28 libdyld.dylib 0x0397970d 开始 + 1 ) libc++abi.dylib:以 _NSCoreDataException 类型的未捕获异常终止

【问题讨论】:

  • 嗨,克雷格。只是一个简单的问题:在每次数据库修改后您是否正确[managedObjectContext save:&error]?因为这是提交并应用您的更改的save: 方法。
  • 你能展示创建获取结果控制器的代码吗?你检查[fetchedResultsController fetchedObjects]了吗?
  • 大家好。是的,managedObjectContext save:&error] 在删除和插入操作之后都会被调用。我很快就会启动开发机器并粘贴来自 fetchedResultsController 的代码。我不确定我是否检查了 fetchedObject 集合,我会将其放入并稍后执行一些测试。
  • 可能是 NSFetchedResultsController 的缓存(如果有)。您是否尝试清除缓存? (例如:deleteCacheWithName:)。更改上下文后是否还调用了performFetch
  • 我没有在这个控制器上使用缓存。不过还是谢谢你的建议。

标签: ios objective-c core-data


【解决方案1】:

嗯,这很奇怪,但我已经完成了我现在需要的工作。当我开始更新后台任务时,我将它自己的 NSManagedObjectContext 传递给它。我将我的主线程设置为这个 NSManagedObjectContext 中保存事件的订阅者,这样我就可以在后台 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 上的保存事件后才会触发通知。我无法解释为什么会发生这种情况,我只能告诉你它正在发生并且这个额外的保存已经解决了它。

【讨论】:

    猜你喜欢
    • 2019-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    • 2018-06-16
    • 2011-07-09
    • 2014-07-08
    • 1970-01-01
    相关资源
    最近更新 更多